diff options
Diffstat (limited to 'test/junit')
67 files changed, 5544 insertions, 892 deletions
diff --git a/test/junit/scala/BoxUnboxTest.scala b/test/junit/scala/BoxUnboxTest.scala new file mode 100644 index 0000000000..162d805a6b --- /dev/null +++ b/test/junit/scala/BoxUnboxTest.scala @@ -0,0 +1,119 @@ +package scala + +import org.junit.Test +import org.junit.Assert._ +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +import scala.tools.testing.AssertUtil._ + +@RunWith(classOf[JUnit4]) +class BoxUnboxTest { + def genericNull[T] = null.asInstanceOf[T] // allowed, see SI-4437, point 2 + + @Test + def boxUnboxInt(): Unit = { + val b = new Integer(1) + val u = 1 + + assertEquals(1.toInt, u) + + assertEquals(Predef.int2Integer(1), b) + assertEquals(1: Integer, b) + assertEquals(Int.box(1), b) + assertEquals(1.asInstanceOf[Object], b) + + assertThrows[ClassCastException]("".asInstanceOf[Integer]) + + assertEquals(Predef.Integer2int(b), u) + assertEquals(b: Int, u) + assertEquals(Int.unbox(b), u) + assertEquals(b.asInstanceOf[Int], u) + assertEquals(b.intValue, u) + assertEquals(b.toInt, u) + intWrapper(b).toInt + + assertThrows[ClassCastException](Int.unbox("")) + assertThrows[ClassCastException]("".asInstanceOf[Int]) + + // null unboxing in various positions + + val n1 = Int.unbox(null) + assertEquals(n1, 0) + val n2 = Predef.Integer2int(null) + assertEquals(n2, 0) + val n3 = (null: Integer): Int + assertEquals(n3, 0) + val n4 = null.asInstanceOf[Int] + assertEquals(n4, 0) + val n5 = null.asInstanceOf[Int] == 0 + assertTrue(n5) + val n6 = null.asInstanceOf[Int] == null // SI-9671 -- should be false, but is true + assertThrows[AssertionError](assertFalse(n6)) // should not throw + val n7 = null.asInstanceOf[Int] != 0 + assertFalse(n7) + val n8 = null.asInstanceOf[Int] != null // SI-9671 -- should be true, but is false + assertThrows[AssertionError](assertTrue(n8)) // should not throw + + val mp = new java.util.HashMap[Int, Int] + val n9 = mp.get(0) + assertEquals(n9, 0) + val n10 = mp.get(0) == null // SI-602 -- maybe related to SI-9671 (test above)? + assertThrows[AssertionError](assertFalse(n10)) // should not throw + + def f(a: Any) = "" + a + val n11 = f(null.asInstanceOf[Int]) // "null", should be "0". probably same cause as SI-602. + assertThrows[AssertionError](assertEquals(n11, "0")) // should not throw + + def n12 = genericNull[Int] + assertEquals(n12, 0) + } + + @Test + def numericConversions(): Unit = { + val i1 = 1L.asInstanceOf[Int] + assertEquals(i1, 1) + assertThrows[ClassCastException] { + val i2 = (1L: Any).asInstanceOf[Int] // SI-1448, should not throw. see also SI-4437 point 1. + assertEquals(i2, 1) + } + } + + @Test + def boxUnboxBoolean(): Unit = { + val n1 = Option(null.asInstanceOf[Boolean]) // SI-7397 -- should be Some(false), but is None + assertThrows[AssertionError](assertEquals(n1, Some(false))) // should not throw + } + + @Test + def boxUnboxUnit(): Unit = { + // should not use assertEquals in this test: it takes two Object parameters. normally, Unit does + // not conform to Object, but for Java-defined methods scalac makes an exception and treats them + // as Any. passing a Unit as Any makes the compiler go through another layer of boxing, so it + // can hide some bugs (where we actually have a null, but the compiler makes it a ()). + + var v = 0 + def eff() = { v = 1 } + def chk() = { assert(v == 1); v = 0 } + + val b = runtime.BoxedUnit.UNIT + + assert(eff() == b); chk() + assert(Unit.box(eff()) == b); chk() + assert(().asInstanceOf[Object] == b) + + Unit.unbox({eff(); b}); chk() + Unit.unbox({eff(); null}); chk() + assertThrows[ClassCastException](Unit.unbox({eff(); ""})); chk() + + val n1 = null.asInstanceOf[Unit] // SI-9066: should be UNIT, but currently null + assertThrows[AssertionError](assert(n1 == b)) // should not throw + + val n2 = null.asInstanceOf[Unit] == b // SI-9066: should be true, but currently false + assertThrows[AssertionError](assert(n2)) // should not throw + + def f(a: Any) = "" + a + val n3 = f(null.asInstanceOf[Unit]) // "null", should be "()". probably same cause as SI-602. + assertThrows[AssertionError](assertEquals(n3, "()")) // should not throw + } +} diff --git a/test/junit/scala/PartialFunctionSerializationTest.scala b/test/junit/scala/PartialFunctionSerializationTest.scala new file mode 100644 index 0000000000..d525b045cd --- /dev/null +++ b/test/junit/scala/PartialFunctionSerializationTest.scala @@ -0,0 +1,36 @@ +package scala + +import org.junit.Test +import org.junit.Assert._ +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@RunWith(classOf[JUnit4]) +class PartialFunctionSerializationTest { + val pf1: PartialFunction[Int, Int] = { + case n if n > 0 => 1 + } + + val pf2: PartialFunction[Int, Int] = { + case n if n <= 0 => 2 + } + + + private def assertSerializable[A,B](fn: A => B) = { + import java.io._ + + new ObjectOutputStream(new ByteArrayOutputStream()).writeObject(fn) + } + + @Test def canSerializeLiteral= assertSerializable(pf1) + + @Test def canSerializeLifted= assertSerializable(pf1.lift) + + @Test def canSerializeOrElse = assertSerializable(pf1 orElse pf2) + + @Test def canSerializeUnlifted = assertSerializable(Function.unlift((x: Int) => Some(x))) + + @Test def canSerializeAndThen = assertSerializable(pf1.andThen((x: Int) => x)) + + @Test def canSerializeEmpty = assertSerializable(PartialFunction.empty) +} diff --git a/test/junit/scala/PredefAutoboxingTest.scala b/test/junit/scala/PredefAutoboxingTest.scala new file mode 100644 index 0000000000..e5d8ded5d4 --- /dev/null +++ b/test/junit/scala/PredefAutoboxingTest.scala @@ -0,0 +1,35 @@ +package scala + +import org.junit.Test +import org.junit.Assert._ +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +import scala.tools.testing.AssertUtil._ + +@RunWith(classOf[JUnit4]) +class PredefAutoboxingTest { + @Test def unboxNullByte() = + assertEquals(Predef.Byte2byte(null), 0.toByte) + + @Test def unboxNullShort() = + assertEquals(Predef.Short2short(null), 0.toShort) + + @Test def unboxNullCharacter() = + assertEquals(Predef.Character2char(null), 0.toChar) + + @Test def unboxNullInteger() = + assertEquals(Predef.Integer2int(null), 0) + + @Test def unboxNullLong() = + assertEquals(Predef.Long2long(null), 0L) + + @Test def unboxNullFloat() = + assertEquals(Predef.Float2float(null), 0F, 0F) + + @Test def unboxNullDouble() = + assertEquals(Predef.Double2double(null), 0D, 0D) + + @Test def unboxNullBoolean() = + assertEquals(Predef.Boolean2boolean(null), false) +} diff --git a/test/junit/scala/StringContextTest.scala b/test/junit/scala/StringContextTest.scala index 7e9e775d58..b5af6de7eb 100644 --- a/test/junit/scala/StringContextTest.scala +++ b/test/junit/scala/StringContextTest.scala @@ -1,6 +1,10 @@ package scala +import java.text.DecimalFormat + +import language.implicitConversions + import org.junit.Test import org.junit.Assert._ import org.junit.runner.RunWith @@ -8,10 +12,32 @@ import org.junit.runners.JUnit4 import scala.tools.testing.AssertUtil._ +object StringContextTestUtils { + private val decimalSeparator: Char = new DecimalFormat().getDecimalFormatSymbols().getDecimalSeparator() + private val numberPattern = """(\d+)\.(\d+.*)""".r + + implicit class StringContextOps(val sc: StringContext) extends AnyVal { + // Use this String interpolator to avoid problems with a locale-dependent decimal mark. + def locally(numbers: String*): String = { + val numbersWithCorrectLocale = numbers.map(applyProperLocale) + sc.s(numbersWithCorrectLocale: _*) + } + + // Handles cases like locally"3.14" - it's prettier than locally"${"3.14"}". + def locally(): String = sc.parts.map(applyProperLocale).mkString + + private def applyProperLocale(number: String): String = { + val numberPattern(intPart, fractionalPartAndSuffix) = number + s"$intPart$decimalSeparator$fractionalPartAndSuffix" + } + } +} + @RunWith(classOf[JUnit4]) class StringContextTest { import StringContext._ + import StringContextTestUtils.StringContextOps @Test def noEscape() = { val s = "string" @@ -65,23 +91,176 @@ class StringContextTest { @Test def fIf() = { val res = f"${if (true) 2.5 else 2.5}%.2f" - val expected = formatUsingCurrentLocale(2.50) + val expected = locally"2.50" assertEquals(expected, res) } @Test def fIfNot() = { val res = f"${if (false) 2.5 else 3.5}%.2f" - val expected = formatUsingCurrentLocale(3.50) + val expected = locally"3.50" assertEquals(expected, res) } @Test def fHeteroArgs() = { val res = f"${3.14}%.2f rounds to ${3}%d" - val expected = formatUsingCurrentLocale(3.14) + " rounds to 3" + val expected = locally"${"3.14"} rounds to 3" assertEquals(expected, res) } - // Use this method to avoid problems with a locale-dependent decimal mark. - // The string interpolation is not used here intentionally as this method is used to test string interpolation. - private def formatUsingCurrentLocale(number: Double, decimalPlaces: Int = 2) = ("%." + decimalPlaces + "f").format(number) + @Test def `f interpolator baseline`(): Unit = { + + implicit def stringToBoolean(s: String): Boolean = java.lang.Boolean.parseBoolean(s) + implicit def stringToChar(s: String): Char = s(0) + implicit def str2fmt(s: String): java.util.Formattable = new java.util.Formattable { + def formatTo(f: java.util.Formatter, g: Int, w: Int, p: Int) = f.format("%s", s) + } + + val b_true = true + val b_false = false + + val i = 42 + + val f_zero = 0.0 + val f_zero_- = -0.0 + + val s = "Scala" + + val fff = new java.util.Formattable { + def formatTo(f: java.util.Formatter, g: Int, w: Int, p: Int) = f.format("4") + } + import java.util.{ Calendar, Locale } + val c = Calendar.getInstance(Locale.US) + c.set(2012, Calendar.MAY, 26) + implicit def strToDate(x: String): Calendar = c + + val ss = List[(String, String)] ( + // 'b' / 'B' (category: general) + // ----------------------------- + f"${b_false}%b" -> "false", + f"${b_true}%b" -> "true", + + f"${null}%b" -> "false", + f"${false}%b" -> "false", + f"${true}%b" -> "true", + f"${true && false}%b" -> "false", + f"${new java.lang.Boolean(false)}%b" -> "false", + f"${new java.lang.Boolean(true)}%b" -> "true", + + f"${null}%B" -> "FALSE", + f"${false}%B" -> "FALSE", + f"${true}%B" -> "TRUE", + f"${new java.lang.Boolean(false)}%B" -> "FALSE", + f"${new java.lang.Boolean(true)}%B" -> "TRUE", + + f"${"true"}%b" -> "true", + f"${"false"}%b"-> "false", + + // 'h' | 'H' (category: general) + // ----------------------------- + f"${null}%h" -> "null", + f"${f_zero}%h" -> "0", + f"${f_zero_-}%h" -> "80000000", + f"${s}%h" -> "4c01926", + + f"${null}%H" -> "NULL", + f"${s}%H" -> "4C01926", + + // 's' | 'S' (category: general) + // ----------------------------- + f"${null}%s" -> "null", + f"${null}%S" -> "NULL", + f"${s}%s" -> "Scala", + f"${s}%S" -> "SCALA", + f"${5}" -> "5", + f"${i}" -> "42", + f"${'foo}" -> "'foo", + + f"${Thread.State.NEW}" -> "NEW", + + // 'c' | 'C' (category: character) + // ------------------------------- + f"${120:Char}%c" -> "x", + f"${120:Byte}%c" -> "x", + f"${120:Short}%c" -> "x", + f"${120:Int}%c" -> "x", + f"${new java.lang.Character('x')}%c" -> "x", + f"${new java.lang.Byte(120:Byte)}%c" -> "x", + f"${new java.lang.Short(120:Short)}%c" -> "x", + f"${new java.lang.Integer(120)}%c" -> "x", + + f"${'x' : java.lang.Character}%c" -> "x", + f"${(120:Byte) : java.lang.Byte}%c" -> "x", + f"${(120:Short) : java.lang.Short}%c" -> "x", + f"${120 : java.lang.Integer}%c" -> "x", + + f"${"Scala"}%c" -> "S", + + // 'd' | 'o' | 'x' | 'X' (category: integral) + // ------------------------------------------ + f"${120:Byte}%d" -> "120", + f"${120:Short}%d" -> "120", + f"${120:Int}%d" -> "120", + f"${120:Long}%d" -> "120", + f"${60 * 2}%d" -> "120", + f"${new java.lang.Byte(120:Byte)}%d" -> "120", + f"${new java.lang.Short(120:Short)}%d" -> "120", + f"${new java.lang.Integer(120)}%d" -> "120", + f"${new java.lang.Long(120)}%d" -> "120", + f"${120 : java.lang.Integer}%d" -> "120", + f"${120 : java.lang.Long}%d" -> "120", + f"${BigInt(120)}%d" -> "120", + + f"${new java.math.BigInteger("120")}%d" -> "120", + + f"${4}%#10X" -> " 0X4", + + f"She is ${fff}%#s feet tall." -> "She is 4 feet tall.", + + f"Just want to say ${"hello, world"}%#s..." -> "Just want to say hello, world...", + + { implicit val strToShort = (s: String) => java.lang.Short.parseShort(s) ; f"${"120"}%d" } -> "120", + { implicit val strToInt = (s: String) => 42 ; f"${"120"}%d" } -> "42", + + // 'e' | 'E' | 'g' | 'G' | 'f' | 'a' | 'A' (category: floating point) + // ------------------------------------------------------------------ + f"${3.4f}%e" -> locally"3.400000e+00", + f"${3.4}%e" -> locally"3.400000e+00", + f"${3.4f : java.lang.Float}%e" -> locally"3.400000e+00", + f"${3.4 : java.lang.Double}%e" -> locally"3.400000e+00", + + f"${BigDecimal(3.4)}%e" -> locally"3.400000e+00", + + f"${new java.math.BigDecimal(3.4)}%e" -> locally"3.400000e+00", + + f"${3}%e" -> locally"3.000000e+00", + f"${3L}%e" -> locally"3.000000e+00", + + // 't' | 'T' (category: date/time) + // ------------------------------- + f"${c}%TD" -> "05/26/12", + f"${c.getTime}%TD" -> "05/26/12", + f"${c.getTime.getTime}%TD" -> "05/26/12", + f"""${"1234"}%TD""" -> "05/26/12", + + // literals and arg indexes + f"%%" -> "%", + f" mind%n------%nmatter" -> + """| mind + |------ + |matter""".stripMargin.lines.mkString(compat.Platform.EOL), + f"${i}%d %<d ${9}%d" -> "42 42 9", + f"${7}%d %<d ${9}%d" -> "7 7 9", + f"${7}%d %2$$d ${9}%d" -> "7 9 9", + + f"${null}%d %<B" -> "null FALSE", + + f"${5: Any}" -> "5", + f"${5}%s%<d" -> "55", + f"${3.14}%s,%<f" -> locally"3.14,${"3.140000"}", + + f"z" -> "z" + ) + + for ((f, s) <- ss) assertEquals(s, f) + } } diff --git a/test/junit/scala/collection/IteratorTest.scala b/test/junit/scala/collection/IteratorTest.scala index 329c85127a..4df29e36c0 100644 --- a/test/junit/scala/collection/IteratorTest.scala +++ b/test/junit/scala/collection/IteratorTest.scala @@ -135,6 +135,20 @@ class IteratorTest { assertEquals(3, List(1, 2, 3, 4, 5).iterator.indexWhere { x: Int => x >= 4 }) assertEquals(-1, List(1, 2, 3, 4, 5).iterator.indexWhere { x: Int => x >= 16 }) } + @Test def indexOfFrom(): Unit = { + assertEquals(1, List(1, 2, 3, 4, 5).iterator.indexOf(2, 0)) + assertEquals(1, List(1, 2, 3, 4, 5).iterator.indexOf(2, 1)) + assertEquals(-1, List(1, 2, 3, 4, 5).iterator.indexOf(2, 2)) + assertEquals(4, List(1, 2, 3, 2, 1).iterator.indexOf(1, 1)) + assertEquals(1, List(1, 2, 3, 2, 1).iterator.indexOf(2, 1)) + } + @Test def indexWhereFrom(): Unit = { + assertEquals(1, List(1, 2, 3, 4, 5).iterator.indexWhere(_ == 2, 0)) + assertEquals(1, List(1, 2, 3, 4, 5).iterator.indexWhere(_ == 2, 1)) + assertEquals(-1, List(1, 2, 3, 4, 5).iterator.indexWhere(_ == 2, 2)) + assertEquals(4, List(1, 2, 3, 2, 1).iterator.indexWhere(_ < 2, 1)) + assertEquals(1, List(1, 2, 3, 2, 1).iterator.indexWhere(_ <= 2, 1)) + } // iterator-iterate-lazy.scala // was java.lang.UnsupportedOperationException: tail of empty list @Test def iterateIsSufficientlyLazy(): Unit = { @@ -154,6 +168,14 @@ class IteratorTest { results += (Stream from 1).toIterator.drop(10).toStream.drop(10).toIterator.next() assertSameElements(List(1,1,21), results) } + // SI-8552 + @Test def indexOfShouldWorkForTwoParams(): Unit = { + assertEquals(1, List(1, 2, 3).iterator.indexOf(2, 0)) + assertEquals(-1, List(5 -> 0).iterator.indexOf(5, 0)) + assertEquals(0, List(5 -> 0).iterator.indexOf((5, 0))) + assertEquals(-1, List(5 -> 0, 9 -> 2, 0 -> 3).iterator.indexOf(9, 2)) + assertEquals(1, List(5 -> 0, 9 -> 2, 0 -> 3).iterator.indexOf(9 -> 2)) + } // SI-9332 @Test def spanExhaustsLeadingIterator(): Unit = { def it = Iterator.iterate(0)(_ + 1).take(6) diff --git a/test/junit/scala/collection/ReusableBuildersTest.scala b/test/junit/scala/collection/ReusableBuildersTest.scala new file mode 100644 index 0000000000..8dd1a37adf --- /dev/null +++ b/test/junit/scala/collection/ReusableBuildersTest.scala @@ -0,0 +1,48 @@ +package scala.collection + +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.junit.Test + +/* Tests various maps by making sure they all agree on the same answers. */ +@RunWith(classOf[JUnit4]) +class ReusableBuildersTest { + // GrowingBuilders are NOT reusable but can clear themselves + @Test + def test_SI8648() { + val b = collection.mutable.HashSet.newBuilder[Int] + b += 3 + b.clear + assert(!b.isInstanceOf[collection.mutable.ReusableBuilder[_,_]]) + assert(b.isInstanceOf[collection.mutable.GrowingBuilder[_,_]]) + assert(b.result == Set[Int]()) + } + + // ArrayBuilders ARE reusable, regardless of whether they returned their internal array or not + @Test + def test_SI9564() { + val b = Array.newBuilder[Float] + b += 3f + val three = b.result + b.clear + b ++= (1 to 16).map(_.toFloat) + val sixteen = b.result + b.clear + b += 0f + val zero = b.result + assert(b.isInstanceOf[collection.mutable.ReusableBuilder[_,_]]) + assert(three.toList == 3 :: Nil) + assert(sixteen.toList == (1 to 16)) + assert(zero.toList == 0 :: Nil) + } + + @Test + def test_reusability() { + val bl = List.newBuilder[String] + val bv = Vector.newBuilder[String] + val ba = collection.mutable.ArrayBuffer.newBuilder[String] + assert(bl.isInstanceOf[collection.mutable.ReusableBuilder[_, _]]) + assert(bv.isInstanceOf[collection.mutable.ReusableBuilder[_, _]]) + assert(!ba.isInstanceOf[collection.mutable.ReusableBuilder[_, _]]) + } +} diff --git a/test/junit/scala/collection/SeqViewTest.scala b/test/junit/scala/collection/SeqViewTest.scala new file mode 100644 index 0000000000..24474fc4b9 --- /dev/null +++ b/test/junit/scala/collection/SeqViewTest.scala @@ -0,0 +1,16 @@ +package scala.collection + +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.junit.Assert._ +import org.junit.Test + +@RunWith(classOf[JUnit4]) +class SeqViewTest { + + @Test + def test_SI8691() { + // Really just testing to make sure ++: doesn't throw an exception + assert( Seq(1,2) ++: Seq(3,4).view == Seq(1,2,3,4) ) + } +} diff --git a/test/junit/scala/collection/SetMapConsistencyTest.scala b/test/junit/scala/collection/SetMapConsistencyTest.scala index 261c11a98b..eb864a8449 100644 --- a/test/junit/scala/collection/SetMapConsistencyTest.scala +++ b/test/junit/scala/collection/SetMapConsistencyTest.scala @@ -66,6 +66,8 @@ class SetMapConsistencyTest { def boxMhm[A] = new BoxMutableMap[A, cm.HashMap[A, Int]](new cm.HashMap[A, Int], "mutable.HashMap") def boxMohm[A] = new BoxMutableMap[A, cm.OpenHashMap[A, Int]](new cm.OpenHashMap[A, Int], "mutable.OpenHashMap") + + def boxMtm[A: Ordering] = new BoxMutableMap[A, cm.TreeMap[A, Int]](new cm.TreeMap[A, Int], "mutable.TreeMap") def boxMarm[A <: AnyRef] = new BoxMutableMap[A, cm.AnyRefMap[A, Int]](new cm.AnyRefMap[A, Int](_ => -1), "mutable.AnyRefMap") { private def arm: cm.AnyRefMap[A, Int] = m.asInstanceOf[cm.AnyRefMap[A, Int]] @@ -188,7 +190,9 @@ class SetMapConsistencyTest { def boxMbs = new BoxMutableSet[Int, cm.BitSet](new cm.BitSet, "mutable.BitSet") def boxMhs[A] = new BoxMutableSet[A, cm.HashSet[A]](new cm.HashSet[A], "mutable.HashSet") - + + def boxMts[A: Ordering] = new BoxMutableSet[A, cm.TreeSet[A]](new cm.TreeSet[A], "mutable.TreeSet") + def boxJavaS[A] = new BoxMutableSet[A, cm.Set[A]]((new java.util.HashSet[A]).asScala, "java.util.HashSet") { override def adders = 3 override def subbers = 1 @@ -315,7 +319,7 @@ class SetMapConsistencyTest { @Test def churnIntMaps() { val maps = Array[() => MapBox[Int]]( - () => boxMlm[Int], () => boxMhm[Int], () => boxMohm[Int], () => boxJavaM[Int], + () => boxMlm[Int], () => boxMhm[Int], () => boxMohm[Int], () => boxMtm[Int], () => boxJavaM[Int], () => boxIim, () => boxIhm[Int], () => boxIlm[Int], () => boxItm[Int] ) assert( maps.sliding(2).forall{ ms => churn(ms(0)(), ms(1)(), intKeys, 2000) } ) @@ -325,7 +329,7 @@ class SetMapConsistencyTest { def churnLongMaps() { val maps = Array[() => MapBox[Long]]( () => boxMjm, () => boxIjm, () => boxJavaM[Long], - () => boxMlm[Long], () => boxMhm[Long], () => boxMohm[Long], () => boxIhm[Long], () => boxIlm[Long] + () => boxMlm[Long], () => boxMhm[Long], () => boxMtm[Long], () => boxMohm[Long], () => boxIhm[Long], () => boxIlm[Long] ) assert( maps.sliding(2).forall{ ms => churn(ms(0)(), ms(1)(), longKeys, 10000) } ) } @@ -352,7 +356,7 @@ class SetMapConsistencyTest { def churnIntSets() { val sets = Array[() => MapBox[Int]]( () => boxMhm[Int], () => boxIhm[Int], () => boxJavaS[Int], - () => boxMbs, () => boxMhs[Int], () => boxIbs, () => boxIhs[Int], () => boxIls[Int], () => boxIts[Int] + () => boxMbs, () => boxMhs[Int], () => boxMts[Int], () => boxIbs, () => boxIhs[Int], () => boxIls[Int], () => boxIts[Int] ) assert( sets.sliding(2).forall{ ms => churn(ms(0)(), ms(1)(), smallKeys, 1000, valuer = _ => 0) } ) } @@ -529,4 +533,15 @@ class SetMapConsistencyTest { assert(nit == 4) assert(nfe == 4) } + + @Test + def test_SI8727() { + import scala.tools.testing.AssertUtil._ + type NSEE = NoSuchElementException + val map = Map(0 -> "zero", 1 -> "one") + val m = map.filterKeys(i => if (map contains i) true else throw new NSEE) + assert{ (m contains 0) && (m get 0).nonEmpty } + assertThrows[NSEE]{ m contains 2 } + assertThrows[NSEE]{ m get 2 } + } } diff --git a/test/junit/scala/collection/convert/NullSafetyTest.scala b/test/junit/scala/collection/convert/NullSafetyTest.scala new file mode 100644 index 0000000000..173568408c --- /dev/null +++ b/test/junit/scala/collection/convert/NullSafetyTest.scala @@ -0,0 +1,279 @@ +package scala.collection.convert + +import java.{util => ju, lang => jl} +import ju.{concurrent => juc} + +import org.junit.Test +import org.junit.experimental.runners.Enclosed +import org.junit.runner.RunWith + +import collection.convert.ImplicitConversions._ +import scala.collection.JavaConverters._ +import scala.collection.{mutable, concurrent} + +@RunWith(classOf[Enclosed]) +object NullSafetyTest { + + /* + * Pertinent: SI-9113 + * Tests to insure that wrappers return null instead of wrapping it as a collection + */ + + class ToScala { + + @Test def testIteratorWrapping(): Unit = { + val nullJIterator: ju.Iterator[AnyRef] = null + val iterator: Iterator[AnyRef] = nullJIterator + + assert(iterator == null) + } + + @Test def testEnumerationWrapping(): Unit = { + val nullJEnumeration: ju.Enumeration[AnyRef] = null + val enumeration: Iterator[AnyRef] = nullJEnumeration + + assert(enumeration == null) + } + + @Test def testIterableWrapping(): Unit = { + val nullJIterable: jl.Iterable[AnyRef] = null + val iterable: Iterable[AnyRef] = nullJIterable + + assert(iterable == null) + } + + @Test def testCollectionWrapping(): Unit = { + val nullJCollection: ju.Collection[AnyRef] = null + val collection: Iterable[AnyRef] = nullJCollection + + assert(collection == null) + } + + @Test def testBufferWrapping(): Unit = { + val nullJList: ju.List[AnyRef] = null + val buffer: mutable.Buffer[AnyRef] = nullJList + + assert(buffer == null) + } + + @Test def testSetWrapping(): Unit = { + val nullJSet: ju.Set[AnyRef] = null + val set: mutable.Set[AnyRef] = nullJSet + + assert(set == null) + } + + @Test def testMapWrapping(): Unit = { + val nullJMap: ju.Map[AnyRef, AnyRef] = null + val map: mutable.Map[AnyRef, AnyRef] = nullJMap + + assert(map == null) + } + + @Test def testConcurrentMapWrapping(): Unit = { + val nullJConMap: juc.ConcurrentMap[AnyRef, AnyRef] = null + val conMap: concurrent.Map[AnyRef, AnyRef] = nullJConMap + + assert(conMap == null) + } + + @Test def testDictionaryWrapping(): Unit = { + val nullJDict: ju.Dictionary[AnyRef, AnyRef] = null + val dict: mutable.Map[AnyRef, AnyRef] = nullJDict + + assert(dict == null) + } + + + @Test def testPropertyWrapping(): Unit = { + val nullJProps: ju.Properties = null + val props: mutable.Map[String, String] = nullJProps + + assert(props == null) + } + + @Test def testIteratorDecoration(): Unit = { + val nullJIterator: ju.Iterator[AnyRef] = null + + assert(nullJIterator.asScala == null) + } + + @Test def testEnumerationDecoration(): Unit = { + val nullJEnumeration: ju.Enumeration[AnyRef] = null + + assert(nullJEnumeration.asScala == null) + } + + @Test def testIterableDecoration(): Unit = { + val nullJIterable: jl.Iterable[AnyRef] = null + + assert(nullJIterable.asScala == null) + } + + @Test def testCollectionDecoration(): Unit = { + val nullJCollection: ju.Collection[AnyRef] = null + + assert(nullJCollection.asScala == null) + } + + @Test def testBufferDecoration(): Unit = { + val nullJBuffer: ju.List[AnyRef] = null + + assert(nullJBuffer.asScala == null) + } + + @Test def testSetDecoration(): Unit = { + val nullJSet: ju.Set[AnyRef] = null + + assert(nullJSet.asScala == null) + } + + @Test def testMapDecoration(): Unit = { + val nullJMap: ju.Map[AnyRef, AnyRef] = null + + assert(nullJMap.asScala == null) + } + + @Test def testConcurrentMapDecoration(): Unit = { + val nullJConMap: juc.ConcurrentMap[AnyRef, AnyRef] = null + + assert(nullJConMap.asScala == null) + } + + @Test def testDictionaryDecoration(): Unit = { + val nullJDict: ju.Dictionary[AnyRef, AnyRef] = null + + assert(nullJDict.asScala == null) + } + + @Test def testPropertiesDecoration(): Unit = { + val nullJProperties: ju.Properties = null + + assert(nullJProperties.asScala == null) + } + } + + class ToJava { + + @Test def testIteratorWrapping(): Unit = { + val nullIterator: Iterator[AnyRef] = null + val jIterator: ju.Iterator[AnyRef] = nullIterator + + assert(jIterator == null) + } + + @Test def testEnumerationWrapping(): Unit = { + val nullEnumeration: Iterator[AnyRef] = null + val enumeration: ju.Iterator[AnyRef] = nullEnumeration + + assert(enumeration == null) + } + + @Test def testIterableWrapping(): Unit = { + val nullIterable: Iterable[AnyRef] = null + val iterable: jl.Iterable[AnyRef] = asJavaIterable(nullIterable) + + assert(iterable == null) + } + + @Test def testCollectionWrapping(): Unit = { + val nullCollection: Iterable[AnyRef] = null + val collection: ju.Collection[AnyRef] = nullCollection + + assert(collection == null) + } + + @Test def testBufferWrapping(): Unit = { + val nullList: mutable.Buffer[AnyRef] = null + val buffer: ju.List[AnyRef] = nullList + + assert(buffer == null) + } + + @Test def testSetWrapping(): Unit = { + val nullSet: mutable.Set[AnyRef] = null + val set: ju.Set[AnyRef] = nullSet + + assert(set == null) + } + + @Test def testMapWrapping(): Unit = { + val nullMap: mutable.Map[AnyRef, AnyRef] = null + val map: ju.Map[AnyRef, AnyRef] = nullMap + + assert(map == null) + } + + @Test def testConcurrentMapWrapping(): Unit = { + val nullConMap: concurrent.Map[AnyRef, AnyRef] = null + val conMap: juc.ConcurrentMap[AnyRef, AnyRef] = nullConMap + + assert(conMap == null) + } + + @Test def testDictionaryWrapping(): Unit = { + val nullDict: mutable.Map[AnyRef, AnyRef] = null + val dict: ju.Dictionary[AnyRef, AnyRef] = nullDict + + assert(dict == null) + } + + // Implicit conversion to ju.Properties is not available + + @Test def testIteratorDecoration(): Unit = { + val nullIterator: Iterator[AnyRef] = null + + assert(nullIterator.asJava == null) + } + + @Test def testEnumerationDecoration(): Unit = { + val nullEnumeration: Iterator[AnyRef] = null + + assert(nullEnumeration.asJavaEnumeration == null) + } + + @Test def testIterableDecoration(): Unit = { + val nullIterable: Iterable[AnyRef] = null + + assert(nullIterable.asJava == null) + } + + @Test def testCollectionDecoration(): Unit = { + val nullCollection: Iterable[AnyRef] = null + + assert(nullCollection.asJavaCollection == null) + } + + @Test def testBufferDecoration(): Unit = { + val nullBuffer: mutable.Buffer[AnyRef] = null + + assert(nullBuffer.asJava == null) + } + + @Test def testSetDecoration(): Unit = { + val nullSet: Set[AnyRef] = null + + assert(nullSet.asJava == null) + } + + @Test def testMapDecoration(): Unit = { + val nullMap: mutable.Map[AnyRef, AnyRef] = null + + assert(nullMap.asJava == null) + } + + @Test def testConcurrentMapDecoration(): Unit = { + val nullConMap: concurrent.Map[AnyRef, AnyRef] = null + + assert(nullConMap.asJava == null) + } + + @Test def testDictionaryDecoration(): Unit = { + val nullDict: mutable.Map[AnyRef, AnyRef] = null + + assert(nullDict.asJavaDictionary == null) + } + + // Decorator conversion to ju.Properties is not available + } +} diff --git a/test/junit/scala/collection/convert/WrapperSerializationTest.scala b/test/junit/scala/collection/convert/WrapperSerializationTest.scala new file mode 100644 index 0000000000..d398be806a --- /dev/null +++ b/test/junit/scala/collection/convert/WrapperSerializationTest.scala @@ -0,0 +1,29 @@ +package scala.collection.convert + +import org.junit.Assert._ +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@RunWith(classOf[JUnit4]) +class WrapperSerializationTest { + def ser(a: AnyRef) = { + val baos = new java.io.ByteArrayOutputStream + (new java.io.ObjectOutputStream(baos)).writeObject(a) + baos + } + def des(baos: java.io.ByteArrayOutputStream): AnyRef = { + val bais = new java.io.ByteArrayInputStream(baos.toByteArray) + (new java.io.ObjectInputStream(bais)).readObject() + } + def serdes(a: AnyRef): Boolean = a == des(ser(a)) + + @Test + def test_SI8911() { + import scala.collection.JavaConverters._ + assert( serdes(scala.collection.mutable.ArrayBuffer(1,2).asJava) ) + assert( serdes(Seq(1,2).asJava) ) + assert( serdes(Set(1,2).asJava) ) + assert( serdes(Map(1 -> "one", 2 -> "two").asJava) ) + } +} diff --git a/test/junit/scala/collection/immutable/SetTests.scala b/test/junit/scala/collection/immutable/SetTest.scala index 28c7864359..4029c98009 100644 --- a/test/junit/scala/collection/immutable/SetTests.scala +++ b/test/junit/scala/collection/immutable/SetTest.scala @@ -6,7 +6,7 @@ import org.junit.runner.RunWith import org.junit.runners.JUnit4 @RunWith(classOf[JUnit4]) -class SetTests { +class SetTest { @Test def test_SI8346_toSet_soundness(): Unit = { val any2stringadd = "Disabled string conversions so as not to get confused!" diff --git a/test/junit/scala/collection/immutable/StreamTest.scala b/test/junit/scala/collection/immutable/StreamTest.scala new file mode 100644 index 0000000000..1b257aabc4 --- /dev/null +++ b/test/junit/scala/collection/immutable/StreamTest.scala @@ -0,0 +1,126 @@ +package scala.collection.immutable + +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.junit.Test +import org.junit.Assert._ + +import scala.ref.WeakReference +import scala.util.Try + +@RunWith(classOf[JUnit4]) +class StreamTest { + + @Test + def t6727_and_t6440_and_8627(): Unit = { + assertTrue(Stream.continually(()).filter(_ => true).take(2) == Seq((), ())) + assertTrue(Stream.continually(()).filterNot(_ => false).take(2) == Seq((), ())) + assertTrue(Stream(1,2,3,4,5).filter(_ < 4) == Seq(1,2,3)) + assertTrue(Stream(1,2,3,4,5).filterNot(_ > 4) == Seq(1,2,3,4)) + assertTrue(Stream.from(1).filter(_ > 4).take(3) == Seq(5,6,7)) + assertTrue(Stream.from(1).filterNot(_ <= 4).take(3) == Seq(5,6,7)) + } + + /** Test helper to verify that the given Stream operation allows + * GC of the head during processing of the tail. + */ + def assertStreamOpAllowsGC(op: (=> Stream[Int], Int => Unit) => Any, f: Int => Unit): Unit = { + val msgSuccessGC = "GC success" + val msgFailureGC = "GC failure" + + // A stream of 500 elements at most. We will test that the head can be collected + // while processing the tail. After each element we will GC and wait 10 ms, so a + // failure to collect will take roughly 5 seconds. + val ref = WeakReference( Stream.from(1).take(500) ) + + def gcAndThrowIfCollected(n: Int): Unit = { + System.gc() // try to GC + Thread.sleep(10) // give it 10 ms + if (ref.get.isEmpty) throw new RuntimeException(msgSuccessGC) // we're done if head collected + f(n) + } + + val res = Try { op(ref(), gcAndThrowIfCollected) }.failed // success is indicated by an + val msg = res.map(_.getMessage).getOrElse(msgFailureGC) // exception with expected message + // failure is indicated by no + assertTrue(msg == msgSuccessGC) // exception, or one with different message + } + + @Test + def foreach_allows_GC() { + assertStreamOpAllowsGC(_.foreach(_), _ => ()) + } + + @Test + def filter_all_foreach_allows_GC() { + assertStreamOpAllowsGC(_.filter(_ => true).foreach(_), _ => ()) + } + + @Test // SI-8990 + def withFilter_after_first_foreach_allows_GC: Unit = { + assertStreamOpAllowsGC(_.withFilter(_ > 1).foreach(_), _ => ()) + } + + @Test // SI-8990 + def withFilter_after_first_withFilter_foreach_allows_GC: Unit = { + assertStreamOpAllowsGC(_.withFilter(_ > 1).withFilter(_ < 100).foreach(_), _ => ()) + } + + @Test // SI-8990 + def withFilter_can_retry_after_exception_thrown_in_filter: Unit = { + // use mutable state to control an intermittent failure in filtering the Stream + var shouldThrow = true + + val wf = Stream.from(1).take(10).withFilter { n => + if (shouldThrow && n == 5) throw new RuntimeException("n == 5") else n > 5 + } + + assertTrue( Try { wf.map(identity) }.isFailure ) // throws on n == 5 + + shouldThrow = false // won't throw next time + + assertTrue( wf.map(identity).length == 5 ) // success instead of NPE + } + + /** Test helper to verify that the given Stream operation is properly lazy in the tail */ + def assertStreamOpLazyInTail(op: (=> Stream[Int]) => Stream[Int], expectedEvaluated: List[Int]): Unit = { + // mutable state to record every strict evaluation + var evaluated: List[Int] = Nil + + def trackEffectsOnNaturals: Stream[Int] = { + def loop(i: Int): Stream[Int] = { evaluated ++= List(i); i #:: loop(i + 1) } + loop(1) + } + + // call op on a stream which records every strict evaluation + val result = op(trackEffectsOnNaturals) + + assertTrue( evaluated == expectedEvaluated ) + } + + @Test // SI-9134 + def filter_map_properly_lazy_in_tail: Unit = { + assertStreamOpLazyInTail(_.filter(_ % 2 == 0).map(identity), List(1, 2)) + } + + @Test // SI-9134 + def withFilter_map_properly_lazy_in_tail: Unit = { + assertStreamOpLazyInTail(_.withFilter(_ % 2 == 0).map(identity), List(1, 2)) + } + + @Test + def test_si9379() { + class Boom { + private var i = -1 + def inc = { + i += 1 + if (i > 1000) throw new NoSuchElementException("Boom! Too many elements!") + i + } + } + val b = new Boom + val s = Stream.continually(b.inc) + // zipped.toString must allow s to short-circuit evaluation + assertTrue((s, s).zipped.toString contains s.toString) + } +} diff --git a/test/junit/scala/collection/immutable/StringLikeTest.scala b/test/junit/scala/collection/immutable/StringLikeTest.scala index 3722bdfe4d..50be638b89 100644 --- a/test/junit/scala/collection/immutable/StringLikeTest.scala +++ b/test/junit/scala/collection/immutable/StringLikeTest.scala @@ -28,10 +28,16 @@ class StringLikeTest { @Test def testSplitEdgeCases: Unit = { + val high = 0xD852.toChar + val low = 0xDF62.toChar + val surrogatepair = List(high, low).mkString + val twopairs = surrogatepair + "_" + surrogatepair + AssertUtil.assertSameElements("abcd".split('d'), Array("abc")) // not Array("abc", "") AssertUtil.assertSameElements("abccc".split('c'), Array("ab")) // not Array("ab", "", "", "") AssertUtil.assertSameElements("xxx".split('x'), Array[String]()) // not Array("", "", "", "") AssertUtil.assertSameElements("".split('x'), Array("")) // not Array() AssertUtil.assertSameElements("--ch--omp--".split("-"), Array("", "", "ch", "", "omp")) // All the cases! + AssertUtil.assertSameElements(twopairs.split(high), Array(twopairs)) //don't split on characters that are half a surrogate pair } } diff --git a/test/junit/scala/collection/mutable/BitSetTest.scala b/test/junit/scala/collection/mutable/BitSetTest.scala index d56cc45601..e832194989 100644 --- a/test/junit/scala/collection/mutable/BitSetTest.scala +++ b/test/junit/scala/collection/mutable/BitSetTest.scala @@ -28,4 +28,11 @@ class BitSetTest { littleBitSet &= bigBitSet assert(littleBitSet.toBitMask.length < bigBitSet.toBitMask.length, "Needlessly extended the size of bitset on &=") } + + @Test def test_SI8647() { + val bs = BitSet() + bs.map(_ + 1) // Just needs to compile + val xs = bs: SortedSet[Int] + xs.map(_ + 1) // Also should compile (did before) + } } diff --git a/test/junit/scala/collection/mutable/OpenHashMapTest.scala b/test/junit/scala/collection/mutable/OpenHashMapTest.scala index 1459c14d78..9b5c20e01a 100644 --- a/test/junit/scala/collection/mutable/OpenHashMapTest.scala +++ b/test/junit/scala/collection/mutable/OpenHashMapTest.scala @@ -28,7 +28,7 @@ class OpenHashMapTest { val fieldMirror = mirror.reflect(m).reflectField(termSym) */ // Use Java reflection instead for now. - val field = m.getClass.getDeclaredField("scala$collection$mutable$OpenHashMap$$deleted") + val field = m.getClass.getDeclaredField("deleted") field.setAccessible(true) m.put(0, 0) diff --git a/test/junit/scala/collection/mutable/PriorityQueueTest.scala b/test/junit/scala/collection/mutable/PriorityQueueTest.scala index a14f1bf4c8..faedcf11f0 100644 --- a/test/junit/scala/collection/mutable/PriorityQueueTest.scala +++ b/test/junit/scala/collection/mutable/PriorityQueueTest.scala @@ -14,6 +14,12 @@ class PriorityQueueTest { priorityQueue.enqueue(elements :_*) @Test + def orderingReverseReverse() { + val pq = new mutable.PriorityQueue[Nothing]()((_,_)=>42) + assert(pq.ord eq pq.reverse.reverse.ord) + } + + @Test def canSerialize() { val outputStream = new ByteArrayOutputStream() new ObjectOutputStream(outputStream).writeObject(priorityQueue) @@ -27,6 +33,7 @@ class PriorityQueueTest { val objectInputStream = new ObjectInputStream(new ByteArrayInputStream(bytes)) val deserializedPriorityQueue = objectInputStream.readObject().asInstanceOf[PriorityQueue[Int]] + //correct sequencing is also tested here: assert(deserializedPriorityQueue.dequeueAll == elements.sorted.reverse) } } diff --git a/test/junit/scala/io/SourceTest.scala b/test/junit/scala/io/SourceTest.scala index 3138a4589c..3fe48940a0 100644 --- a/test/junit/scala/io/SourceTest.scala +++ b/test/junit/scala/io/SourceTest.scala @@ -28,6 +28,10 @@ class SourceTest { @Test def canIterateLines() = { assertEquals(sampler.lines.size, (Source fromString sampler).getLines.size) } + @Test def loadFromResource() = { + val res = Source.fromResource("rootdoc.txt") + assertTrue("No classpath resource found", res.getLines().size > 5) + } @Test def canCustomizeReporting() = { class CapitalReporting(is: InputStream) extends BufferedSource(is) { override def report(pos: Int, msg: String, out: PrintStream): Unit = { diff --git a/test/junit/scala/issues/BytecodeTest.scala b/test/junit/scala/issues/BytecodeTest.scala new file mode 100644 index 0000000000..cf5c7f9ec3 --- /dev/null +++ b/test/junit/scala/issues/BytecodeTest.scala @@ -0,0 +1,478 @@ +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 + +object BytecodeTest extends ClearAfterClass.Clearable { + var compiler = newCompiler() + def clear(): Unit = { compiler = null } +} + +@RunWith(classOf[JUnit4]) +class BytecodeTest extends ClearAfterClass { + ClearAfterClass.stateToClear = BytecodeTest + val compiler = BytecodeTest.compiler + + @Test + def t8731(): Unit = { + val code = + """class C { + | def f(x: Int) = (x: @annotation.switch) match { + | case 1 => 0 + | case 2 => 1 + | case 3 => 2 + | } + | final val K = 10 + | def g(x: Int) = (x: @annotation.switch) match { + | case K => 0 + | case 1 => 10 + | case 2 => 20 + | } + |} + """.stripMargin + + val List(c) = compileClasses(compiler)(code) + + assertTrue(getSingleMethod(c, "f").instructions.count(_.isInstanceOf[TableSwitch]) == 1) + assertTrue(getSingleMethod(c, "g").instructions.count(_.isInstanceOf[LookupSwitch]) == 1) + } + + @Test + def t8926(): Unit = { + import scala.reflect.internal.util.BatchSourceFile + + // this test cannot be implemented using partest because of its mixed-mode compilation strategy: + // partest first compiles all files with scalac, then the java files, and then again the scala + // using the output classpath. this shadows the bug SI-8926. + + val annotA = + """import java.lang.annotation.Retention; + |import java.lang.annotation.RetentionPolicy; + |@Retention(RetentionPolicy.RUNTIME) + |public @interface AnnotA { } + """.stripMargin + val annotB = "public @interface AnnotB { }" + + val scalaSrc = + """@AnnotA class A + |@AnnotB class B + """.stripMargin + + val run = new compiler.Run() + run.compileSources(List(new BatchSourceFile("AnnotA.java", annotA), new BatchSourceFile("AnnotB.java", annotB), new BatchSourceFile("Test.scala", scalaSrc))) + val outDir = compiler.settings.outputDirs.getSingleOutput.get + val outfiles = (for (f <- outDir.iterator if !f.isDirectory) yield (f.name, f.toByteArray)).toList + + def check(classfile: String, annotName: String) = { + val f = (outfiles collect { case (`classfile`, bytes) => AsmUtils.readClass(bytes) }).head + val descs = f.visibleAnnotations.asScala.map(_.desc).toList + assertTrue(descs.toString, descs exists (_ contains annotName)) + } + + check("A.class", "AnnotA") + + // known issue SI-8928: the visibility of AnnotB should be CLASS, but annotation classes without + // a @Retention annotation are currently emitted as RUNTIME. + check("B.class", "AnnotB") + } + + @Test + def t6288bJumpPosition(): Unit = { + val code = + """object Case3 { // 01 + | def unapply(z: Any): Option[Int] = Some(-1) // 02 + | def main(args: Array[String]) { // 03 + | ("": Any) match { // 04 + | case x : String => // 05 + | println("case 0") // 06 println and jump at 6 + | case _ => // 07 + | println("default") // 08 println and jump at 8 + | } // 09 + | println("done") // 10 + | } + |} + """.stripMargin + val List(mirror, module) = compileClasses(compiler)(code) + + val unapplyLineNumbers = getSingleMethod(module, "unapply").instructions.filter(_.isInstanceOf[LineNumber]) + assert(unapplyLineNumbers == List(LineNumber(2, Label(0))), unapplyLineNumbers) + + val expected = List( + LineNumber(4, Label(0)), + LineNumber(5, Label(5)), + Jump(IFEQ, Label(20)), + + LineNumber(6, Label(11)), + Invoke(INVOKEVIRTUAL, "scala/Predef$", "println", "(Ljava/lang/Object;)V", false), + Jump(GOTO, Label(33)), + + LineNumber(5, Label(20)), + Jump(GOTO, Label(24)), + + LineNumber(8, Label(24)), + Invoke(INVOKEVIRTUAL, "scala/Predef$", "println", "(Ljava/lang/Object;)V", false), + Jump(GOTO, Label(33)), + + LineNumber(10, Label(33)), + Invoke(INVOKEVIRTUAL, "scala/Predef$", "println", "(Ljava/lang/Object;)V", false) + ) + + val mainIns = getSingleMethod(module, "main").instructions filter { + case _: LineNumber | _: Invoke | _: Jump => true + case _ => false + } + assertSameCode(mainIns, expected) + } + + @Test + def bytecodeForBranches(): Unit = { + val code = + """class C { + | def t1(b: Boolean) = if (b) 1 else 2 + | def t2(x: Int) = if (x == 393) 1 else 2 + | def t3(a: Array[String], b: AnyRef) = a != b && b == a + | def t4(a: AnyRef) = a == null || null != a + | def t5(a: AnyRef) = (a eq null) || (null ne a) + | def t6(a: Int, b: Boolean) = if ((a == 10) && b || a != 1) 1 else 2 + | def t7(a: AnyRef, b: AnyRef) = a == b + | def t8(a: AnyRef) = Nil == a || "" != a + |} + """.stripMargin + + val List(c) = compileClasses(compiler)(code) + + // t1: no unnecessary GOTOs + assertSameCode(getSingleMethod(c, "t1"), List( + VarOp(ILOAD, 1), Jump(IFEQ, Label(6)), + Op(ICONST_1), Jump(GOTO, Label(9)), + Label(6), Op(ICONST_2), + Label(9), Op(IRETURN))) + + // t2: no unnecessary GOTOs + assertSameCode(getSingleMethod(c, "t2"), List( + VarOp(ILOAD, 1), IntOp(SIPUSH, 393), Jump(IF_ICMPNE, Label(7)), + Op(ICONST_1), Jump(GOTO, Label(10)), + Label(7), Op(ICONST_2), + Label(10), Op(IRETURN))) + + // t3: Array == is translated to reference equality, AnyRef == to null checks and equals + assertSameCode(getSingleMethod(c, "t3"), List( + // Array == + VarOp(ALOAD, 1), VarOp(ALOAD, 2), Jump(IF_ACMPEQ, Label(23)), + // AnyRef == + VarOp(ALOAD, 2), VarOp(ALOAD, 1), VarOp(ASTORE, 3), Op(DUP), Jump(IFNONNULL, Label(14)), + Op(POP), VarOp(ALOAD, 3), Jump(IFNULL, Label(19)), Jump(GOTO, Label(23)), + Label(14), VarOp(ALOAD, 3), Invoke(INVOKEVIRTUAL, "java/lang/Object", "equals", "(Ljava/lang/Object;)Z", false), Jump(IFEQ, Label(23)), + Label(19), Op(ICONST_1), Jump(GOTO, Label(26)), + Label(23), Op(ICONST_0), + Label(26), Op(IRETURN))) + + val t4t5 = List( + VarOp(ALOAD, 1), Jump(IFNULL, Label(6)), + VarOp(ALOAD, 1), Jump(IFNULL, Label(10)), + Label(6), Op(ICONST_1), Jump(GOTO, Label(13)), + Label(10), Op(ICONST_0), + Label(13), Op(IRETURN)) + + // t4: one side is known null, so just a null check on the other + assertSameCode(getSingleMethod(c, "t4"), t4t5) + + // t5: one side known null, so just a null check on the other + assertSameCode(getSingleMethod(c, "t5"), t4t5) + + // t6: no unnecessary GOTOs + assertSameCode(getSingleMethod(c, "t6"), List( + VarOp(ILOAD, 1), IntOp(BIPUSH, 10), Jump(IF_ICMPNE, Label(7)), + VarOp(ILOAD, 2), Jump(IFNE, Label(12)), + Label(7), VarOp(ILOAD, 1), Op(ICONST_1), Jump(IF_ICMPEQ, Label(16)), + Label(12), Op(ICONST_1), Jump(GOTO, Label(19)), + Label(16), Op(ICONST_2), + Label(19), Op(IRETURN))) + + // t7: universal equality + assertInvoke(getSingleMethod(c, "t7"), "scala/runtime/BoxesRunTime", "equals") + + // t8: no null checks invoking equals on modules and constants + assertSameCode(getSingleMethod(c, "t8"), List( + Field(GETSTATIC, "scala/collection/immutable/Nil$", "MODULE$", "Lscala/collection/immutable/Nil$;"), VarOp(ALOAD, 1), Invoke(INVOKEVIRTUAL, "java/lang/Object", "equals", "(Ljava/lang/Object;)Z", false), Jump(IFNE, Label(10)), + Ldc(LDC, ""), VarOp(ALOAD, 1), Invoke(INVOKEVIRTUAL, "java/lang/Object", "equals", "(Ljava/lang/Object;)Z", false), Jump(IFNE, Label(14)), + Label(10), Op(ICONST_1), Jump(GOTO, Label(17)), + 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/BytecodeTests.scala b/test/junit/scala/issues/BytecodeTests.scala deleted file mode 100644 index d4ed063a03..0000000000 --- a/test/junit/scala/issues/BytecodeTests.scala +++ /dev/null @@ -1,80 +0,0 @@ -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.partest.ASMConverters._ - -@RunWith(classOf[JUnit4]) -class BytecodeTests { - val compiler = newCompiler() - - @Test - def t8731(): Unit = { - val code = - """class C { - | def f(x: Int) = (x: @annotation.switch) match { - | case 1 => 0 - | case 2 => 1 - | case 3 => 2 - | } - | final val K = 10 - | def g(x: Int) = (x: @annotation.switch) match { - | case K => 0 - | case 1 => 10 - | case 2 => 20 - | } - |} - """.stripMargin - - val List(c) = compileClasses(compiler)(code) - - assertTrue(getSingleMethod(c, "f").instructions.count(_.isInstanceOf[TableSwitch]) == 1) - assertTrue(getSingleMethod(c, "g").instructions.count(_.isInstanceOf[LookupSwitch]) == 1) - } - - @Test - def t8926(): Unit = { - import scala.reflect.internal.util.BatchSourceFile - - // this test cannot be implemented using partest because of its mixed-mode compilation strategy: - // partest first compiles all files with scalac, then the java files, and then again the scala - // using the output classpath. this shadows the bug SI-8926. - - val annotA = - """import java.lang.annotation.Retention; - |import java.lang.annotation.RetentionPolicy; - |@Retention(RetentionPolicy.RUNTIME) - |public @interface AnnotA { } - """.stripMargin - val annotB = "public @interface AnnotB { }" - - val scalaSrc = - """@AnnotA class A - |@AnnotB class B - """.stripMargin - - val compiler = newCompiler() - val run = new compiler.Run() - run.compileSources(List(new BatchSourceFile("AnnotA.java", annotA), new BatchSourceFile("AnnotB.java", annotB), new BatchSourceFile("Test.scala", scalaSrc))) - val outDir = compiler.settings.outputDirs.getSingleOutput.get - val outfiles = (for (f <- outDir.iterator if !f.isDirectory) yield (f.name, f.toByteArray)).toList - - def check(classfile: String, annotName: String) = { - val f = (outfiles collect { case (`classfile`, bytes) => AsmUtils.readClass(bytes) }).head - val descs = f.visibleAnnotations.asScala.map(_.desc).toList - assertTrue(descs.toString, descs exists (_ contains annotName)) - } - - check("A.class", "AnnotA") - - // known issue SI-8928: the visibility of AnnotB should be CLASS, but annotation classes without - // a @Retention annotation are currently emitted as RUNTIME. - check("B.class", "AnnotB") - } -} diff --git a/test/junit/scala/issues/OptimizedBytecodeTest.scala b/test/junit/scala/issues/OptimizedBytecodeTest.scala new file mode 100644 index 0000000000..1555e8945a --- /dev/null +++ b/test/junit/scala/issues/OptimizedBytecodeTest.scala @@ -0,0 +1,375 @@ +package scala.issues + +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.nsc.backend.jvm.{AsmUtils, CodeGenTools} + +import CodeGenTools._ +import scala.tools.partest.ASMConverters +import ASMConverters._ +import AsmUtils._ + +import scala.tools.testing.ClearAfterClass + +object OptimizedBytecodeTest extends ClearAfterClass.Clearable { + val args = "-Yopt:l:classpath -Yopt-warnings" + var compiler = newCompiler(extraArgs = args) + def clear(): Unit = { compiler = null } +} + +@RunWith(classOf[JUnit4]) +class OptimizedBytecodeTest extends ClearAfterClass { + ClearAfterClass.stateToClear = OptimizedBytecodeTest + + val compiler = OptimizedBytecodeTest.compiler + + @Test + def t2171(): Unit = { + val code = + """class C { + | final def m(msg: => String) = try 0 catch { case ex: Throwable => println(msg) } + | def t(): Unit = while (true) m("...") + |} + """.stripMargin + val List(c) = compileClasses(compiler)(code) + assertSameCode(getSingleMethod(c, "t"), List(Label(0), Jump(GOTO, Label(0)))) + } + + @Test + def t3430(): Unit = { + val code = + """class C { + | final def m(f: String => Boolean) = f("a") + | def t(): Boolean = + | m { s1 => + | m { s2 => + | while (true) { } + | true + | } + | } + |} + """.stripMargin + val List(c) = compileClasses(compiler)(code) + + assertSameSummary(getSingleMethod(c, "t"), List( + LDC, ASTORE, ALOAD /*0*/, ALOAD /*1*/, "C$$$anonfun$1", IRETURN)) + assertSameSummary(getSingleMethod(c, "C$$$anonfun$1"), List(LDC, "C$$$anonfun$2", IRETURN)) + assertSameSummary(getSingleMethod(c, "C$$$anonfun$2"), List(-1 /*A*/, GOTO /*A*/)) + } + + @Test + def t3252(): Unit = { + val code = + """class C { + | def t(x: Boolean): Thread = { + | g { + | x match { + | case false => Tat.h { } + | } + | } + | } + | + | private def g[T](block: => T) = ??? + |} + |object Tat { + | def h(block: => Unit): Nothing = ??? + |} + """.stripMargin + val List(c, t, tMod) = compileClasses(compiler)(code, allowMessage = _.msg.contains("not be exhaustive")) + assertSameSummary(getSingleMethod(c, "t"), List(GETSTATIC, "$qmark$qmark$qmark", ATHROW)) + } + + @Test + def t6157(): Unit = { + val code = + """class C { + | def t = println(ErrorHandler.defaultIfIOException("String")("String")) + |} + |object ErrorHandler { + | import java.io.IOException + | @inline + | def defaultIfIOException[T](default: => T)(closure: => T): T = try closure catch { + | case e: IOException => default + | } + |} + """.stripMargin + + val msg = + """ErrorHandler$::defaultIfIOException(Lscala/Function0;Lscala/Function0;)Ljava/lang/Object; is annotated @inline but could not be inlined: + |The operand stack at the callsite in C::t()V contains more values than the + |arguments expected by the callee ErrorHandler$::defaultIfIOException(Lscala/Function0;Lscala/Function0;)Ljava/lang/Object;. These values would be discarded + |when entering an exception handler declared in the inlined method.""".stripMargin + + compileClasses(compiler)(code, allowMessage = _.msg == msg) + } + + @Test + def t6547(): Unit = { // "pos" test -- check that it compiles + val code = + """trait ConfigurableDefault[@specialized V] { + | def fillArray(arr: Array[V], v: V) = (arr: Any) match { + | case x: Array[Int] => null + | case x: Array[Long] => v.asInstanceOf[Long] + | } + |} + """.stripMargin + compileClasses(compiler)(code) + } + + @Test + def t8062(): Unit = { + val c1 = + """package warmup + |object Warmup { def filter[A](p: Any => Boolean): Any = filter[Any](p) } + """.stripMargin + val c2 = "class C { def t = warmup.Warmup.filter[Any](x => false) }" + val List(c, _, _) = compileClassesSeparately(List(c1, c2), extraArgs = OptimizedBytecodeTest.args) + assertInvoke(getSingleMethod(c, "t"), "warmup/Warmup$", "filter") + } + + @Test + def t8306(): Unit = { // "pos" test + val code = + """class C { + | def foo: Int = 123 + | lazy val extension: Int = foo match { + | case idx if idx != -1 => 15 + | case _ => 17 + | } + |} + """.stripMargin + compileClasses(compiler)(code) + } + + @Test + def t8359(): Unit = { // "pos" test + // This is a minimization of code that crashed the compiler during bootstrapping + // in the first iteration of https://github.com/scala/scala/pull/4373, the PR + // that adjusted the order of free and declared params in LambdaLift. + + // Was: + // java.lang.AssertionError: assertion failed: + // Record Record(<$anon: Function1>,Map(value a$1 -> Deref(LocalVar(value b)))) does not contain a field value b$1 + // at scala.tools.nsc.Global.assert(Global.scala:262) + // at scala.tools.nsc.backend.icode.analysis.CopyPropagation$copyLattice$State.getFieldNonRecordValue(CopyPropagation.scala:113) + // at scala.tools.nsc.backend.icode.analysis.CopyPropagation$copyLattice$State.getFieldNonRecordValue(CopyPropagation.scala:122) + // at scala.tools.nsc.backend.opt.ClosureElimination$ClosureElim$$anonfun$analyzeMethod$1$$anonfun$apply$2.replaceFieldAccess$1(ClosureElimination.scala:124) + val code = + """package test + |class Typer { + | def bar(a: Boolean, b: Boolean): Unit = { + | @inline + | def baz(): Unit = { + | ((_: Any) => (Typer.this, a, b)).apply("") + | } + | ((_: Any) => baz()).apply("") + | } + |} + """.stripMargin + compileClasses(compiler)(code) + } + + @Test + def t9123(): Unit = { // "pos" test + val code = + """trait Setting { + | type T + | def value: T + |} + |object Test { + | def test(x: Some[Setting]) = x match { + | case Some(dep) => Some(dep.value) map (_ => true) + | } + |} + """.stripMargin + compileClasses(compiler)(code) + } + + @Test + def traitForceInfo(): Unit = { + // This did NOT crash unless it's in the interactive package. + // error: java.lang.AssertionError: assertion failed: trait Contexts.NoContext$ linkedModule: <none>List() + // at scala.Predef$.assert(Predef.scala:160) + // at scala.tools.nsc.symtab.classfile.ClassfileParser$innerClasses$.innerSymbol$1(ClassfileParser.scala:1211) + // at scala.tools.nsc.symtab.classfile.ClassfileParser$innerClasses$.classSymbol(ClassfileParser.scala:1223) + // at scala.tools.nsc.symtab.classfile.ClassfileParser.classNameToSymbol(ClassfileParser.scala:489) + // at scala.tools.nsc.symtab.classfile.ClassfileParser.sig2type$1(ClassfileParser.scala:757) + // at scala.tools.nsc.symtab.classfile.ClassfileParser.sig2type$1(ClassfileParser.scala:789) + val code = + """package scala.tools.nsc + |package interactive + | + |trait MyContextTrees { + | val self: Global + | val NoContext = self.analyzer.NoContext + |} + """.stripMargin + compileClasses(compiler)(code) + } + + @Test + def t9160(): Unit = { + val code = + """class C { + | def getInt: Int = 0 + | def t(trees: Object): Int = { + | trees match { + | case Some(elems) => + | case tree => getInt + | } + | 55 + | } + |} + """.stripMargin + val List(c) = compileClasses(compiler)(code) + assertSameSummary(getSingleMethod(c, "t"), List( + ALOAD /*1*/, INSTANCEOF /*Some*/, IFNE /*A*/, + ALOAD /*0*/, "getInt", POP, + -1 /*A*/, BIPUSH, IRETURN)) + } + + @Test + def t8796(): Unit = { + val code = + """final class C { + | def pr(): Unit = () + | def t(index: Int): Unit = index match { + | case 0 => pr() + | case 1 => pr() + | case _ => t(index - 2) + | } + |} + """.stripMargin + val List(c) = compileClasses(compiler)(code) + assertSameSummary(getSingleMethod(c, "t"), List( + -1 /*A*/, ILOAD /*1*/, TABLESWITCH, + -1, ALOAD, "pr", RETURN, + -1, ALOAD, "pr", RETURN, + -1, ILOAD, ICONST_2, ISUB, ISTORE, GOTO /*A*/)) + } + + @Test + def t8524(): Unit = { + val c1 = + """package library + |object Library { + | @inline def pleaseInlineMe() = 1 + | object Nested { @inline def pleaseInlineMe() = 2 } + |} + """.stripMargin + + val c2 = + """class C { + | def t = library.Library.pleaseInlineMe() + library.Library.Nested.pleaseInlineMe() + |} + """.stripMargin + + val cls = compileClassesSeparately(List(c1, c2), extraArgs = OptimizedBytecodeTest.args) + val c = cls.find(_.name == "C").get + assertSameSummary(getSingleMethod(c, "t"), List( + GETSTATIC, IFNONNULL, ACONST_NULL, ATHROW, // module load and null checks not yet eliminated + -1, ICONST_1, GETSTATIC, IFNONNULL, ACONST_NULL, ATHROW, + -1, ICONST_2, IADD, IRETURN)) + } + + @Test + def privateInline(): Unit = { + val code = + """final class C { + | private var x1 = false + | var x2 = false + | + | @inline private def wrapper1[T](body: => T): T = { + | val saved = x1 + | x1 = true + | try body + | finally x1 = saved + | } + | + | @inline private def wrapper2[T](body: => T): T = { + | val saved = x2 + | x2 = true + | try body + | finally x2 = saved + | } + | // inlined + | def f1a() = wrapper1(5) + | // not inlined: even after inlining `identity`, the Predef module is already on the stack for the + | // subsequent null check (the receiver of an inlined method, in this case Predef, is checked for + | // nullness, to ensure an NPE is thrown) + | def f1b() = identity(wrapper1(5)) + | + | def f2a() = wrapper2(5) // inlined + | def f2b() = identity(wrapper2(5)) // not inlined + |} + """.stripMargin + val List(c) = compileClasses(compiler)(code, allowMessage = _.msg.contains("exception handler declared in the inlined method")) + assertInvoke(getSingleMethod(c, "f1a"), "C", "C$$$anonfun$1") + assertInvoke(getSingleMethod(c, "f1b"), "C", "wrapper1") + assertInvoke(getSingleMethod(c, "f2a"), "C", "C$$$anonfun$3") + assertInvoke(getSingleMethod(c, "f2b"), "C", "wrapper2") + } + + @Test + def t7060(): Unit = { + val code = + """class C { + | @inline final def mbarray_apply_minibox(array: Any, tag: Byte): Long = + | if (tag == 0) array.asInstanceOf[Array[Long]](0) + | else array.asInstanceOf[Array[Byte]](0).toLong + | + | def t = mbarray_apply_minibox(null, 0) + |} + """.stripMargin + val List(c) = compileClasses(compiler)(code) + assertNoInvoke(getSingleMethod(c, "t")) + } + + @Test + def t8315(): Unit = { + val code = + """class C { + | def t(as: Listt): Unit = { + | map(as, (_: Any) => return) + | } + | final def map(x: Listt, f: Any => Any): Any = { + | if (x eq Nill) "" else f("") + | } + |} + |object Nill extends Listt + |class Listt + """.stripMargin + val List(c, nil, nilMod, listt) = compileClasses(compiler)(code) + assertInvoke(getSingleMethod(c, "t"), "C", "C$$$anonfun$1") + } + + @Test + def t8315b(): Unit = { + val code = + """class C { + | def crash: Unit = { + | val key = "" + | try map(new F(key)) + | catch { case _: Throwable => } + | } + | final def map(f: F): Any = f.apply("") + |} + |final class F(key: String) { + | final def apply(a: Any): Any = throw new RuntimeException(key) + |} + """.stripMargin + val List(c, f) = compileClasses(compiler)(code) + assertInvoke(getSingleMethod(c, "crash"), "C", "map") + } + + @Test + def optimiseEnablesNewOpt(): Unit = { + val code = """class C { def t = (1 to 10) foreach println }""" + val List(c) = readAsmClasses(compile(newCompiler(extraArgs = "-optimise -deprecation"))(code, allowMessage = _.msg.contains("is deprecated"))) + assertInvoke(getSingleMethod(c, "t"), "C", "C$$$anonfun$1") // range-foreach inlined from classpath + } +} diff --git a/test/junit/scala/issues/RunTest.scala b/test/junit/scala/issues/RunTest.scala new file mode 100644 index 0000000000..0605947e63 --- /dev/null +++ b/test/junit/scala/issues/RunTest.scala @@ -0,0 +1,162 @@ +package scala.issues + +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.junit.{AfterClass, BeforeClass, Test} +import org.junit.Assert._ + +import scala.reflect.runtime._ +import scala.tools.reflect.ToolBox +import scala.tools.testing.ClearAfterClass + +object RunTest extends ClearAfterClass.Clearable { + var toolBox = universe.runtimeMirror(getClass.getClassLoader).mkToolBox() + override def clear(): Unit = { toolBox = null } + + // definitions for individual tests + + class VC(val x: Any) extends AnyVal +} + +@RunWith(classOf[JUnit4]) +class RunTest extends ClearAfterClass { + ClearAfterClass.stateToClear = RunTest + + def run[T](code: String): T = { + val tb = RunTest.toolBox + tb.eval(tb.parse(code)).asInstanceOf[T] + } + + @Test + def classOfValueClassAlias(): Unit = { + val code = + """import scala.issues.RunTest.VC + |type aVC = VC + |type aInt = Int + |type aInteger = Integer + |classOf[VC] == classOf[aVC] && + | classOf[aInt] == classOf[Int] && + | classOf[aInteger] == classOf[Integer] && + | classOf[aInt] != classOf[aInteger] + """.stripMargin + assertTrue(run[Boolean](code)) + } + + @Test + def classOfFinalVal(): Unit = { + val code = + """class C { + | final val a1 = classOf[Int] + | final val b1 = classOf[List[_]] + | final val c1 = classOf[List[String]] + | final val d1 = classOf[Array[Int]] + | final val e1 = classOf[Array[List[_]]] + | final val f1 = classOf[Array[_]] + | + | val a2 = classOf[Int] + | val b2 = classOf[List[_]] + | val c2 = classOf[List[String]] + | val d2 = classOf[Array[Int]] + | val e2 = classOf[Array[List[_]]] + | val f2 = classOf[Array[_]] + | + | val listC = Class.forName("scala.collection.immutable.List") + | + | val compare = List( + | (a1, a2, Integer.TYPE), + | (b1, b2, listC), + | (c1, c2, listC), + | (d1, d2, Array(1).getClass), + | (e1, e2, Array(List()).getClass), + | (f1, f2, new Object().getClass)) + |} + |(new C).compare + """.stripMargin + type K = Class[_] + val cs = run[List[(K, K, K)]](code) + for ((x, y, z) <- cs) { + assertEquals(x, y) + assertEquals(x, z) + } + } + + @Test + def t9702(): Unit = { + val code = + """import javax.annotation.Resource + |import scala.issues.RunTest.VC + |class C { + | type aList[K] = List[K] + | type aVC = VC + | type aInt = Int + | type aInteger = Integer + | @Resource(`type` = classOf[List[Int]]) def a = 0 + | @Resource(`type` = classOf[List[_]]) def b = 0 + | @Resource(`type` = classOf[aList[_]]) def c = 0 + | @Resource(`type` = classOf[Int]) def d = 0 + | @Resource(`type` = classOf[aInt]) def e = 0 + | @Resource(`type` = classOf[Integer]) def f = 0 + | @Resource(`type` = classOf[aInteger]) def g = 0 + | @Resource(`type` = classOf[VC]) def h = 0 + | @Resource(`type` = classOf[aVC]) def i = 0 + | @Resource(`type` = classOf[Array[Int]]) def j = 0 + | @Resource(`type` = classOf[Array[List[_]]]) def k = 0 + |} + |val c = classOf[C] + |def typeArg(meth: String) = c.getDeclaredMethod(meth).getDeclaredAnnotation(classOf[Resource]).`type` + |('a' to 'k').toList.map(_.toString).map(typeArg) + """.stripMargin + + val l = Class.forName("scala.collection.immutable.List") + val i = Integer.TYPE + val ig = new Integer(1).getClass + val v = new RunTest.VC(1).getClass + val ai = Array(1).getClass + val al = Array(List()).getClass + + // sanity checks + assertEquals(i, classOf[Int]) + assertNotEquals(i, ig) + + assertEquals(run[List[Class[_]]](code), + List(l, l, l, i, i, ig, ig, v, v, ai, al)) + } + + @Test + def annotationInfoNotErased(): Unit = { + val code = + """import javax.annotation.Resource + |import scala.annotation.meta.getter + |class C { + | type Rg = Resource @getter + | @(Resource @getter)(`type` = classOf[Int]) def a = 0 + | @Rg(`type` = classOf[Int]) def b = 0 + |} + |val c = classOf[C] + |def typeArg(meth: String) = c.getDeclaredMethod(meth).getDeclaredAnnotation(classOf[Resource]).`type` + |List("a", "b") map typeArg + |""".stripMargin + + 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 + } + + @Test + def classOfUnitConstant(): Unit = { + val code = + """abstract class A { def f: Class[_] } + |class C extends A { final val f = classOf[Unit] } + |val c = new C + |(c.f, (c: A).f) + """.stripMargin + val u = Void.TYPE + assertEquals(run[(Class[_], Class[_])](code), (u, u)) + } +} diff --git a/test/junit/scala/math/BigDecimalTest.scala b/test/junit/scala/math/BigDecimalTest.scala index a9e2481f37..5de02cbe0c 100644 --- a/test/junit/scala/math/BigDecimalTest.scala +++ b/test/junit/scala/math/BigDecimalTest.scala @@ -260,4 +260,9 @@ class BigDecimalTest { testPrecision() testRounded() } + + @Test + def testIsComparable() { + assert(BigDecimal(0.1).isInstanceOf[java.lang.Comparable[_]]) + } } diff --git a/test/junit/scala/math/BigIntTest.scala b/test/junit/scala/math/BigIntTest.scala new file mode 100644 index 0000000000..5a5694a775 --- /dev/null +++ b/test/junit/scala/math/BigIntTest.scala @@ -0,0 +1,16 @@ +package scala.math + +import java.math.{BigInteger => BI, MathContext => MC} + +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@RunWith(classOf[JUnit4]) +class BigIntTest { + + @Test + def testIsComparable() { + assert(BigInt(1).isInstanceOf[java.lang.Comparable[_]]) + } +} diff --git a/test/junit/scala/reflect/ClassTag.scala b/test/junit/scala/reflect/ClassTagTest.scala index 49022dccda..49022dccda 100644 --- a/test/junit/scala/reflect/ClassTag.scala +++ b/test/junit/scala/reflect/ClassTagTest.scala 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/reflect/internal/TreeGenTest.scala b/test/junit/scala/reflect/internal/TreeGenTest.scala new file mode 100644 index 0000000000..db1ea5cf6a --- /dev/null +++ b/test/junit/scala/reflect/internal/TreeGenTest.scala @@ -0,0 +1,51 @@ +package scala.reflect.internal + +import org.junit.Assert._ +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +import scala.runtime.ScalaRunTime +import scala.tools.nsc.symtab.SymbolTableForUnitTesting + +@RunWith(classOf[JUnit4]) +class TreeGenTest { + object symbolTable extends SymbolTableForUnitTesting + + import symbolTable._ + + @Test + def attributedRefToTopLevelMemberNotPrefixedByThis_t9473_a(): Unit = { + val SomeClass = symbolOf[Some[_]] + val ref = gen.mkAttributedRef(SomeClass) + assertEquals("scala.Some", ref.toString) // was scala.this.Some + ref match { + case sel @ Select(pre @ Ident(preName), name) => + assertEquals(TermName("scala"), preName) + assertEquals(TypeName("Some"), name) + assertEquals(SomeClass, sel.symbol) + case _ => fail(showRaw(ref)) + } + } + + @Test + def attributedRefToTopLevelMemberNotPrefixedByThis_t9473_b(): Unit = { + val ScalaRuntimeModule = symbolOf[ScalaRunTime.type].sourceModule + val ref = gen.mkAttributedRef(ScalaRuntimeModule) + assertEquals("scala.runtime.ScalaRunTime", ref.toString) + ref match { + case sel @ Select(Select(Ident(TermName("scala")), TermName("runtime")), TermName("ScalaRunTime")) => + case _ => fail(showRaw(ref)) + } + } + @Test + def attributedRefToTopLevelMemberNotPrefixedByThis_t9473_c(): Unit = { + val DummyImplicitClass = symbolOf[Predef.DummyImplicit] + val ref = gen.mkAttributedRef(DummyImplicitClass) + assertEquals("scala.Predef.DummyImplicit", ref.toString) +// ref match { +// case sel @ Select(Select(Ident(TermName("scala")), TermName("runtime")), TermName("ScalaRunTime")) => +// case _ => fail(showRaw(ref)) +// } + } +} diff --git a/test/junit/scala/reflect/internal/TypesTest.scala b/test/junit/scala/reflect/internal/TypesTest.scala index 95194ef0a4..05a77cfb47 100644 --- a/test/junit/scala/reflect/internal/TypesTest.scala +++ b/test/junit/scala/reflect/internal/TypesTest.scala @@ -1,9 +1,10 @@ package scala.reflect.internal import org.junit.Assert._ -import org.junit.Test +import org.junit.{Assert, Test} import org.junit.runner.RunWith import org.junit.runners.JUnit4 +import scala.collection.mutable import scala.tools.nsc.symtab.SymbolTableForUnitTesting @RunWith(classOf[JUnit4]) @@ -32,4 +33,29 @@ class TypesTest { val uniquelyNarrowed2 = refinedType(boolWithString1narrow2 :: Nil, NoSymbol) assert(uniquelyNarrowed1 =:= uniquelyNarrowed2) } + + @Test + def testTransitivityWithModuleTypeRef(): Unit = { + import rootMirror.EmptyPackageClass + val (module, moduleClass) = EmptyPackageClass.newModuleAndClassSymbol(TermName("O"), NoPosition, 0L) + val minfo = ClassInfoType(List(ObjectTpe), newScope, moduleClass) + module.moduleClass setInfo minfo + module setInfo module.moduleClass.tpe + val tp1 = TypeRef(ThisType(EmptyPackageClass), moduleClass, Nil) + val tp2 = SingleType(ThisType(EmptyPackageClass), module) + val tp3 = ThisType(moduleClass) + val tps = List(tp1, tp2, tp3) + val results = mutable.Buffer[String]() + tps.permutations.foreach { + case ts @ List(a, b, c) => + def tsShownRaw = ts.map(t => showRaw(t)).mkString(", ") + if (a <:< b && b <:< c && !(a <:< c)) results += s"<:< intransitive: $tsShownRaw" + if (a =:= b && b =:= c && !(a =:= c)) results += s"=:= intransitive: $tsShownRaw" + } + results.toList match { + case Nil => // okay + case xs => + Assert.fail(xs.mkString("\n")) + } + } } diff --git a/test/junit/scala/runtime/LambdaDeserializerTest.java b/test/junit/scala/runtime/LambdaDeserializerTest.java new file mode 100644 index 0000000000..069eb4aab6 --- /dev/null +++ b/test/junit/scala/runtime/LambdaDeserializerTest.java @@ -0,0 +1,193 @@ +package scala.runtime; + +import org.junit.Assert; +import org.junit.Test; + +import java.io.Serializable; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.SerializedLambda; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.HashMap; + +public final class LambdaDeserializerTest { + private LambdaHost lambdaHost = new LambdaHost(); + + @Test + public void serializationPrivate() { + F1<Boolean, String> f1 = lambdaHost.lambdaBackedByPrivateImplMethod(); + Assert.assertEquals(f1.apply(true), reconstitute(f1).apply(true)); + } + + @Test + public void serializationStatic() { + F1<Boolean, String> f1 = lambdaHost.lambdaBackedByStaticImplMethod(); + Assert.assertEquals(f1.apply(true), reconstitute(f1).apply(true)); + } + + @Test + public void serializationVirtualMethodReference() { + F1<Boolean, String> f1 = lambdaHost.lambdaBackedByVirtualMethodReference(); + Assert.assertEquals(f1.apply(true), reconstitute(f1).apply(true)); + } + + @Test + public void serializationInterfaceMethodReference() { + F1<I, Object> f1 = lambdaHost.lambdaBackedByInterfaceMethodReference(); + I i = new I() { + }; + Assert.assertEquals(f1.apply(i), reconstitute(f1).apply(i)); + } + + @Test + public void serializationStaticMethodReference() { + F1<Boolean, String> f1 = lambdaHost.lambdaBackedByStaticMethodReference(); + Assert.assertEquals(f1.apply(true), reconstitute(f1).apply(true)); + } + + @Test + public void serializationNewInvokeSpecial() { + F0<Object> f1 = lambdaHost.lambdaBackedByConstructorCall(); + Assert.assertEquals(f1.apply(), reconstitute(f1).apply()); + } + + @Test + public void uncached() { + F0<Object> f1 = lambdaHost.lambdaBackedByConstructorCall(); + F0<Object> reconstituted1 = reconstitute(f1); + F0<Object> reconstituted2 = reconstitute(f1); + Assert.assertNotEquals(reconstituted1.getClass(), reconstituted2.getClass()); + } + + @Test + public void cached() { + HashMap<String, MethodHandle> cache = new HashMap<>(); + F0<Object> f1 = lambdaHost.lambdaBackedByConstructorCall(); + F0<Object> reconstituted1 = reconstitute(f1, cache); + F0<Object> reconstituted2 = reconstitute(f1, cache); + Assert.assertEquals(reconstituted1.getClass(), reconstituted2.getClass()); + } + + @Test + public void cachedStatic() { + HashMap<String, MethodHandle> cache = new HashMap<>(); + F1<Boolean, String> f1 = lambdaHost.lambdaBackedByStaticImplMethod(); + // Check that deserialization of a static lambda always returns the + // same instance. + Assert.assertSame(reconstitute(f1, cache), reconstitute(f1, cache)); + + // (as is the case with regular invocation.) + Assert.assertSame(f1, lambdaHost.lambdaBackedByStaticImplMethod()); + } + + @Test + public void implMethodNameChanged() { + F1<Boolean, String> f1 = lambdaHost.lambdaBackedByStaticImplMethod(); + SerializedLambda sl = writeReplace(f1); + checkIllegalAccess(copySerializedLambda(sl, sl.getImplMethodName() + "___", sl.getImplMethodSignature())); + } + + @Test + public void implMethodSignatureChanged() { + F1<Boolean, String> f1 = lambdaHost.lambdaBackedByStaticImplMethod(); + SerializedLambda sl = writeReplace(f1); + checkIllegalAccess(copySerializedLambda(sl, sl.getImplMethodName(), sl.getImplMethodSignature().replace("Boolean", "Integer"))); + } + + private void checkIllegalAccess(SerializedLambda serialized) { + try { + LambdaDeserializer.deserializeLambda(MethodHandles.lookup(), null, serialized); + throw new AssertionError(); + } catch (IllegalArgumentException iae) { + if (!iae.getMessage().contains("Illegal lambda deserialization")) { + Assert.fail("Unexpected message: " + iae.getMessage()); + } + } + } + + private SerializedLambda copySerializedLambda(SerializedLambda sl, String implMethodName, String implMethodSignature) { + Object[] captures = new Object[sl.getCapturedArgCount()]; + for (int i = 0; i < captures.length; i++) { + captures[i] = sl.getCapturedArg(i); + } + return new SerializedLambda(loadClass(sl.getCapturingClass()), sl.getFunctionalInterfaceClass(), sl.getFunctionalInterfaceMethodName(), + sl.getFunctionalInterfaceMethodSignature(), sl.getImplMethodKind(), sl.getImplClass(), implMethodName, implMethodSignature, + sl.getInstantiatedMethodType(), captures); + } + + private Class<?> loadClass(String className) { + try { + return Class.forName(className.replace('/', '.')); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + private <A, B> A reconstitute(A f1) { + return reconstitute(f1, null); + } + + @SuppressWarnings("unchecked") + private <A, B> A reconstitute(A f1, java.util.HashMap<String, MethodHandle> cache) { + try { + return (A) LambdaDeserializer.deserializeLambda(LambdaHost.lookup(), cache, writeReplace(f1)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private <A> SerializedLambda writeReplace(A f1) { + try { + Method writeReplace = f1.getClass().getDeclaredMethod("writeReplace"); + writeReplace.setAccessible(true); + return (SerializedLambda) writeReplace.invoke(f1); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} + + +interface F1<A, B> extends Serializable { + B apply(A a); +} + +interface F0<A> extends Serializable { + A apply(); +} + +class LambdaHost { + public F1<Boolean, String> lambdaBackedByPrivateImplMethod() { + int local = 42; + return (b) -> Arrays.asList(local, b ? "true" : "false", LambdaHost.this).toString(); + } + + @SuppressWarnings("Convert2MethodRef") + public F1<Boolean, String> lambdaBackedByStaticImplMethod() { + return (b) -> String.valueOf(b); + } + + public F1<Boolean, String> lambdaBackedByStaticMethodReference() { + return String::valueOf; + } + + public F1<Boolean, String> lambdaBackedByVirtualMethodReference() { + return Object::toString; + } + + public F1<I, Object> lambdaBackedByInterfaceMethodReference() { + return I::i; + } + + public F0<Object> lambdaBackedByConstructorCall() { + return String::new; + } + + public static MethodHandles.Lookup lookup() { + return MethodHandles.lookup(); + } +} + +interface I { + default String i() { return "i"; }; +} diff --git a/test/junit/scala/runtime/ScalaRunTimeTest.scala b/test/junit/scala/runtime/ScalaRunTimeTest.scala index 9da197c71a..5bfb12610e 100644 --- a/test/junit/scala/runtime/ScalaRunTimeTest.scala +++ b/test/junit/scala/runtime/ScalaRunTimeTest.scala @@ -5,66 +5,66 @@ import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 -/** Tests for the private class DefaultPromise */ +/** Tests for the runtime object ScalaRunTime */ @RunWith(classOf[JUnit4]) class ScalaRunTimeTest { @Test - def testIsTuple() { - import ScalaRunTime.isTuple - def check(v: Any) = { - assertTrue(v.toString, isTuple(v)) + def testStingOf() { + import ScalaRunTime.stringOf + import scala.collection._ + import parallel.ParIterable + + assertEquals("null", stringOf(null)) + assertEquals( "\"\"", stringOf("")) + + assertEquals("abc", stringOf("abc")) + assertEquals("\" abc\"", stringOf(" abc")) + assertEquals("\"abc \"", stringOf("abc ")) + + assertEquals("""Array()""", stringOf(Array.empty[AnyRef])) + assertEquals("""Array()""", stringOf(Array.empty[Int])) + assertEquals("""Array(1, 2, 3)""", stringOf(Array(1, 2, 3))) + assertEquals("""Array(a, "", " c", null)""", stringOf(Array("a", "", " c", null))) + assertEquals("""Array(Array("", 1, Array(5)), Array(1))""", + stringOf(Array(Array("", 1, Array(5)), Array(1)))) + + val map = Map(1->"", 2->"a", 3->" a", 4->null) + assertEquals(s"""${map.stringPrefix}(1 -> "", 2 -> a, 3 -> " a", 4 -> null)""", stringOf(map)) + assertEquals(s"""${map.stringPrefix}(1 -> "", 2 -> a)""", stringOf(map, 2)) + + val iterable = Iterable("a", "", " c", null) + assertEquals(s"""${iterable.stringPrefix}(a, "", " c", null)""", stringOf(iterable)) + assertEquals(s"""${iterable.stringPrefix}(a, "")""", stringOf(iterable, 2)) + + val parIterable = ParIterable("a", "", " c", null) + assertEquals(s"""${parIterable.stringPrefix}(a, "", " c", null)""", stringOf(parIterable)) + assertEquals(s"""${parIterable.stringPrefix}(a, "")""", stringOf(parIterable, 2)) + + val traversable = new Traversable[Int] { + def foreach[U](f: Int => U): Unit = (0 to 3).foreach(f) } + assertEquals(s"${traversable.stringPrefix}(0, 1, 2, 3)", stringOf(traversable)) + assertEquals(s"${traversable.stringPrefix}(0, 1)", stringOf(traversable, 2)) - val s = "" - check(Tuple1(s)) - check((s, s)) - check((s, s, s)) - check((s, s, s, s)) - check((s, s, s, s, s)) - check((s, s, s, s, s, s)) - check((s, s, s, s, s, s, s)) - check((s, s, s, s, s, s, s, s)) - check((s, s, s, s, s, s, s, s, s)) - check((s, s, s, s, s, s, s, s, s, s)) - check((s, s, s, s, s, s, s, s, s, s, s)) - check((s, s, s, s, s, s, s, s, s, s, s, s)) - check((s, s, s, s, s, s, s, s, s, s, s, s, s)) - check((s, s, s, s, s, s, s, s, s, s, s, s, s, s)) - check((s, s, s, s, s, s, s, s, s, s, s, s, s, s, s)) - check((s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s)) - check((s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s)) - check((s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s)) - check((s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s)) - check((s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s)) - check((s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s)) - check((s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s)) + val tuple1 = Tuple1(0) + assertEquals("(0,)", stringOf(tuple1)) + assertEquals("(0,)", stringOf(tuple1, 0)) + assertEquals("(Array(0),)", stringOf(Tuple1(Array(0)))) - // some specialized variants will have mangled classnames - check(Tuple1(0)) - check((0, 0)) - check((0, 0, 0)) - check((0, 0, 0, 0)) - check((0, 0, 0, 0, 0)) - check((0, 0, 0, 0, 0, 0)) - check((0, 0, 0, 0, 0, 0, 0)) - check((0, 0, 0, 0, 0, 0, 0, 0)) - check((0, 0, 0, 0, 0, 0, 0, 0, 0)) - check((0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) - check((0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) - check((0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) - check((0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) - check((0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) - check((0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) - check((0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) - check((0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) - check((0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) - check((0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) - check((0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) - check((0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) - check((0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) + val tuple2 = Tuple2(0, 1) + assertEquals("(0,1)", stringOf(tuple2)) + assertEquals("(0,1)", stringOf(tuple2, 0)) + assertEquals("(Array(0),1)", stringOf((Array(0), 1))) - case class C() - val c = new C() - assertFalse(c.toString, isTuple(c)) + val tuple3 = Tuple3(0, 1, 2) + assertEquals("(0,1,2)", stringOf(tuple3)) + assertEquals("(0,1,2)", stringOf(tuple3, 0)) + assertEquals("(Array(0),1,2)", stringOf((Array(0), 1, 2))) + + val x = new Object { + override def toString(): String = "this is the stringOf string" + } + assertEquals(stringOf(x), "this is the stringOf string") + assertEquals(stringOf(x, 2), "this is the stringOf string") } } diff --git a/test/junit/scala/sys/process/t7350.scala b/test/junit/scala/sys/process/t7350.scala new file mode 100644 index 0000000000..9fdcac8ccc --- /dev/null +++ b/test/junit/scala/sys/process/t7350.scala @@ -0,0 +1,298 @@ +package scala.sys.process + +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.junit.Test +import java.io.{InputStream, OutputStream, PipedInputStream, PipedOutputStream, ByteArrayInputStream, + ByteArrayOutputStream, IOException, Closeable} +import java.lang.reflect.InvocationTargetException +import scala.concurrent.{Await, Future} +import scala.concurrent.duration.{Duration, SECONDS} +import scala.concurrent.ExecutionContext.Implicits.global +import scala.util.control.Exception.ignoring + +// Each test normally ends in a moment, but for failure cases, waits until one second. + +@RunWith(classOf[JUnit4]) +class PipedProcessTest { + class ProcessMock(error: Boolean) extends Process { + var destroyCount = 0 + def isAlive() = false + def exitValue(): Int = { + if (error) { + throw new InterruptedException() + } + 0 + } + def destroy(): Unit = { destroyCount += 1 } + } + + class ProcessBuilderMock(process: Process, error: Boolean) extends ProcessBuilder.AbstractBuilder { + override def run(io: ProcessIO): Process = { + if (error) { + throw new IOException() + } + process + } + } + + class PipeSinkMock extends Process.PipeSink("PipeSinkMock") { + var releaseCount = 0 + override val pipe = null + override val sink = null + override def run(): Unit = {} + override def connectOut(out: OutputStream): Unit = {} + override def connectIn(pipeOut: PipedOutputStream): Unit = {} + override def release(): Unit = { releaseCount += 1 } + } + + class PipeSourceMock extends Process.PipeSource("PipeSourceMock") { + var releaseCount = 0 + override val pipe = null + override val source = null + override def run(): Unit = {} + override def connectIn(in: InputStream): Unit = {} + override def connectOut(sink: Process.PipeSink): Unit = {} + override def release(): Unit = { releaseCount += 1 } + } + + class PipedProcesses(a: ProcessBuilder, b: ProcessBuilder, defaultIO: ProcessIO, toError: Boolean) + extends Process.PipedProcesses(a, b, defaultIO, toError) { + def callRunAndExitValue(source: Process.PipeSource, sink: Process.PipeSink) = { + val m = classOf[Process.PipedProcesses].getDeclaredMethod("runAndExitValue", classOf[Process.PipeSource], classOf[Process.PipeSink]) + m.setAccessible(true) + try m.invoke(this, source, sink).asInstanceOf[Option[Int]] + catch { + case err: InvocationTargetException => throw err.getTargetException + } + } + } + + // PipedProcesses need not to release resources when it normally end + @Test + def normallyEnd() { + val io = BasicIO(false, ProcessLogger(_ => ())) + val source = new PipeSourceMock + val sink = new PipeSinkMock + val a = new ProcessMock(error = false) + val b = new ProcessMock(error = false) + val p = new PipedProcesses(new ProcessBuilderMock(a, error = false), new ProcessBuilderMock(b, error = false), io, false) + val f = Future { + p.callRunAndExitValue(source, sink) + } + Await.result(f, Duration(1, SECONDS)) + assert(source.releaseCount == 0) + assert(sink.releaseCount == 0) + assert(a.destroyCount == 0) + assert(b.destroyCount == 0) + } + + // PipedProcesses must release resources when b.run() failed + @Test + def bFailed() { + val io = BasicIO(false, ProcessLogger(_ => ())) + val source = new PipeSourceMock + val sink = new PipeSinkMock + val a = new ProcessMock(error = false) + val b = new ProcessMock(error = false) + val p = new PipedProcesses(new ProcessBuilderMock(a, error = false), new ProcessBuilderMock(b, error = true), io, false) + val f = Future { + ignoring(classOf[IOException]) { + p.callRunAndExitValue(source, sink) + } + } + Await.result(f, Duration(1, SECONDS)) + assert(source.releaseCount == 1) + assert(sink.releaseCount == 1) + assert(a.destroyCount == 0) + assert(b.destroyCount == 0) + } + + // PipedProcesses must release resources when a.run() failed + @Test + def aFailed() { + val io = BasicIO(false, ProcessLogger(_ => ())) + val source = new PipeSourceMock + val sink = new PipeSinkMock + val a = new ProcessMock(error = false) + val b = new ProcessMock(error = false) + val p = new PipedProcesses(new ProcessBuilderMock(a, error = true), new ProcessBuilderMock(b, error = false), io, false) + val f = Future { + ignoring(classOf[IOException]) { + p.callRunAndExitValue(source, sink) + } + } + Await.result(f, Duration(1, SECONDS)) + assert(source.releaseCount == 1) + assert(sink.releaseCount == 1) + assert(a.destroyCount == 0) + assert(b.destroyCount == 1) + } + + // PipedProcesses must release resources when interrupted during waiting for first.exitValue() + @Test + def firstInterrupted() { + val io = BasicIO(false, ProcessLogger(_ => ())) + val source = new PipeSourceMock + val sink = new PipeSinkMock + val a = new ProcessMock(error = true) + val b = new ProcessMock(error = false) + val p = new PipedProcesses(new ProcessBuilderMock(a, error = false), new ProcessBuilderMock(b, error = false), io, false) + val f = Future { + p.callRunAndExitValue(source, sink) + } + Await.result(f, Duration(1, SECONDS)) + assert(source.releaseCount == 1) + assert(sink.releaseCount == 1) + assert(a.destroyCount == 1) + assert(b.destroyCount == 1) + } + + // PipedProcesses must release resources when interrupted during waiting for second.exitValue() + @Test + def secondInterrupted() { + val io = BasicIO(false, ProcessLogger(_ => ())) + val source = new PipeSourceMock + val sink = new PipeSinkMock + val a = new ProcessMock(error = false) + val b = new ProcessMock(error = true) + val p = new PipedProcesses(new ProcessBuilderMock(a, error = false), new ProcessBuilderMock(b, error = false), io, false) + val f = Future { + p.callRunAndExitValue(source, sink) + } + Await.result(f, Duration(1, SECONDS)) + assert(source.releaseCount == 1) + assert(sink.releaseCount == 1) + assert(a.destroyCount == 1) + assert(b.destroyCount == 1) + } +} + +@RunWith(classOf[JUnit4]) +class PipeSourceSinkTest { + def throwsIOException(f: => Unit) = { + try { f; false } + catch { case _: IOException => true } + } + + class PipeSink extends Process.PipeSink("TestPipeSink") { + def ensureRunloopStarted() = { + while (sink.size() > 0) { + Thread.sleep(1) + } + } + def isReleased = { + val field = classOf[Process.PipeSink].getDeclaredField("pipe") + field.setAccessible(true) + val pipe = field.get(this).asInstanceOf[PipedInputStream] + !this.isAlive && throwsIOException { pipe.read() } + } + } + + class PipeSource extends Process.PipeSource("TestPipeSource") { + def ensureRunloopStarted() = { + while (source.size() > 0) { + Thread.sleep(1) + } + } + def isReleased = { + val field = classOf[Process.PipeSource].getDeclaredField("pipe") + field.setAccessible(true) + val pipe = field.get(this).asInstanceOf[PipedOutputStream] + !this.isAlive && throwsIOException { pipe.write(1) } + } + } + + trait CloseChecking extends Closeable { + var closed = false + override def close() = closed = true + } + class DebugOutputStream extends ByteArrayOutputStream with CloseChecking + class DebugInputStream(s: String) extends ByteArrayInputStream(s.getBytes()) with CloseChecking + class DebugInfinityInputStream extends InputStream with CloseChecking { + def read() = 1 + } + + def sourceSink() = { + val source = new PipeSource + val sink = new PipeSink + source connectOut sink + source.start() + sink.start() + (source, sink) + } + + // PipeSource and PipeSink must release resources when it normally end + @Test + def normallyEnd() { + val in = new DebugInputStream("aaa") + val (source, sink) = sourceSink() + val out = new DebugOutputStream + source connectIn in + sink connectOut out + val f = Future { + source.join() + sink.join() + } + Await.result(f, Duration(1, SECONDS)) + assert(in.closed == true) + assert(out.closed == true) + assert(source.isReleased == true) + assert(sink.isReleased == true) + } + + // PipeSource and PipeSink must release resources when interrupted during waiting for source.take() + @Test + def sourceInterrupted() { + val (source, sink) = sourceSink() + val out = new DebugOutputStream + sink connectOut out + val f = Future { + sink.ensureRunloopStarted() + source.release() + sink.release() + } + Await.result(f, Duration(1, SECONDS)) + assert(out.closed == true) + assert(source.isReleased == true) + assert(sink.isReleased == true) + } + + // PipeSource and PipeSink must release resources when interrupted during waiting for sink.take() + @Test + def sinkInterrupted() { + val in = new DebugInputStream("aaa") + val (source, sink) = sourceSink() + source connectIn in + val f = Future { + source.ensureRunloopStarted() + source.release() + sink.release() + } + Await.result(f, Duration(1, SECONDS)) + assert(in.closed == true) + assert(source.isReleased == true) + assert(sink.isReleased == true) + } + + // PipeSource and PipeSink must release resources when interrupted during copy streams" + @Test + def runloopInterrupted() { + val in = new DebugInfinityInputStream + val (source, sink) = sourceSink() + val out = new DebugOutputStream + source connectIn in + sink connectOut out + val f = Future { + source.ensureRunloopStarted() + sink.ensureRunloopStarted() + source.release() + sink.release() + } + Await.result(f, Duration(1, SECONDS)) + assert(in.closed == true) + assert(out.closed == true) + assert(source.isReleased == true) + assert(sink.isReleased == true) + } +} diff --git a/test/junit/scala/tools/nsc/backend/jvm/BTypesTest.scala b/test/junit/scala/tools/nsc/backend/jvm/BTypesTest.scala index 6ada0e20fb..8b8e2b36de 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/BTypesTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/BTypesTest.scala @@ -12,7 +12,7 @@ import scala.tools.testing.ClearAfterClass object BTypesTest extends ClearAfterClass.Clearable { var compiler = { - val comp = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:l:none") + val comp = newCompiler(extraArgs = "-Yopt:l:none") new comp.Run() // initializes some of the compiler comp.exitingDelambdafy(comp.scalaPrimitives.init()) // needed: it's only done when running the backend, and we don't actually run the compiler comp.exitingDelambdafy(comp.genBCode.bTypes.initializeCoreBTypes()) diff --git a/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala b/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala index ee9580c1c3..389e5b2ead 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala @@ -48,11 +48,13 @@ object CodeGenTools { resetOutput(compiler) compiler } - + def newCompilerWithoutVirtualOutdir(defaultArgs: String = "-usejavacp", extraArgs: String = ""): Global = { - val settings = new Settings() + def showError(s: String) = throw new Exception(s) + val settings = new Settings(showError) val args = (CommandLineParser tokenize defaultArgs) ++ (CommandLineParser tokenize extraArgs) - settings.processArguments(args, processAll = true) + val (_, nonSettingsArgs) = settings.processArguments(args, processAll = true) + if (nonSettingsArgs.nonEmpty) showError("invalid compiler flags: " + nonSettingsArgs.mkString(" ")) new Global(settings, new StoreReporter) } @@ -93,6 +95,23 @@ object CodeGenTools { getGeneratedClassfiles(compiler.settings.outputDirs.getSingleOutput.get) } + def compileTransformed(compiler: Global)(scalaCode: String, javaCode: List[(String, String)] = Nil, beforeBackend: compiler.Tree => compiler.Tree): List[(String, Array[Byte])] = { + compiler.settings.stopBefore.value = "jvm" :: Nil + val run = newRun(compiler) + import compiler._ + val scalaUnit = newCompilationUnit(scalaCode, "unitTestSource.scala") + val javaUnits = javaCode.map(p => newCompilationUnit(p._1, p._2)) + val units = scalaUnit :: javaUnits + run.compileUnits(units, run.parserPhase) + compiler.settings.stopBefore.value = Nil + scalaUnit.body = beforeBackend(scalaUnit.body) + checkReport(compiler, _ => false) + val run1 = newRun(compiler) + run1.compileUnits(units, run1.phaseNamed("jvm")) + checkReport(compiler, _ => false) + getGeneratedClassfiles(compiler.settings.outputDirs.getSingleOutput.get) + } + /** * Compile multiple Scala files separately into a single output directory. * @@ -145,13 +164,60 @@ object CodeGenTools { convertMethod(m) } + def assertSameCode(method: Method, expected: List[Instruction]): Unit = assertSameCode(method.instructions.dropNonOp, expected) def assertSameCode(actual: List[Instruction], expected: List[Instruction]): Unit = { - assertTrue(s"\nExpected: $expected\nActual : $actual", actual === expected) + assert(actual === expected, s"\nExpected: $expected\nActual : $actual") + } + + def assertSameSummary(method: Method, expected: List[Any]): Unit = assertSameSummary(method.instructions, expected) + def assertSameSummary(actual: List[Instruction], expected: List[Any]): Unit = { + def expectedString = expected.map({ + case s: String => s""""$s"""" + case i: Int => opcodeToString(i, i) + }).mkString("List(", ", ", ")") + assert(actual.summary == expected, s"\nFound : ${actual.summaryText}\nExpected: $expectedString") + } + + def assertNoInvoke(m: Method): Unit = assertNoInvoke(m.instructions) + def assertNoInvoke(ins: List[Instruction]): Unit = { + assert(!ins.exists(_.isInstanceOf[Invoke]), ins.stringLines) + } + + def assertInvoke(m: Method, receiver: String, method: String): Unit = assertInvoke(m.instructions, receiver, method) + def assertInvoke(l: List[Instruction], receiver: String, method: String): Unit = { + assert(l.exists { + case Invoke(_, `receiver`, `method`, _, _) => true + case _ => false + }, l.stringLines) + } + + def assertDoesNotInvoke(m: Method, method: String): Unit = assertDoesNotInvoke(m.instructions, method) + def assertDoesNotInvoke(l: List[Instruction], method: String): Unit = { + assert(!l.exists { + case i: Invoke => i.name == method + case _ => false + }, l.stringLines) + } + + def assertInvokedMethods(m: Method, expected: List[String]): Unit = assertInvokedMethods(m.instructions, expected) + def assertInvokedMethods(l: List[Instruction], expected: List[String]): Unit = { + def quote(l: List[String]) = l.map(s => s""""$s"""").mkString("List(", ", ", ")") + val actual = l collect { case i: Invoke => i.owner + "." + i.name } + assert(actual == expected, s"\nFound : ${quote(actual)}\nExpected: ${quote(expected)}") + } + + def assertNoIndy(m: Method): Unit = assertNoIndy(m.instructions) + def assertNoIndy(l: List[Instruction]) = { + val indy = l collect { case i: InvokeDynamic => i } + assert(indy.isEmpty, indy) } def getSingleMethod(classNode: ClassNode, name: String): Method = convertMethod(classNode.methods.asScala.toList.find(_.name == name).get) + def findAsmMethods(c: ClassNode, p: String => Boolean) = c.methods.iterator.asScala.filter(m => p(m.name)).toList.sortBy(_.name) + def findAsmMethod(c: ClassNode, name: String) = findAsmMethods(c, _ == name).head + /** * Instructions that match `query` when textified. * If `query` starts with a `+`, the next instruction is returned. @@ -159,7 +225,7 @@ object CodeGenTools { def findInstr(method: MethodNode, query: String): List[AbstractInsnNode] = { val useNext = query(0) == '+' val instrPart = if (useNext) query.drop(1) else query - val insns = method.instructions.iterator.asScala.find(i => textify(i) contains instrPart).toList + val insns = method.instructions.iterator.asScala.filter(i => textify(i) contains instrPart).toList if (useNext) insns.map(_.getNext) else insns } @@ -175,4 +241,8 @@ object CodeGenTools { implicit class MortalInstruction(val ins: Instruction) extends AnyVal { def dead: (Instruction, Boolean) = (ins, false) } + + implicit class listStringLines[T](val l: List[T]) extends AnyVal { + def stringLines = l.mkString("\n") + } } diff --git a/test/junit/scala/tools/nsc/backend/jvm/DefaultMethodTest.scala b/test/junit/scala/tools/nsc/backend/jvm/DefaultMethodTest.scala new file mode 100644 index 0000000000..2ce9d21331 --- /dev/null +++ b/test/junit/scala/tools/nsc/backend/jvm/DefaultMethodTest.scala @@ -0,0 +1,43 @@ +package scala.tools.nsc.backend.jvm + +import org.junit.Assert._ +import org.junit.Test + +import scala.collection.JavaConverters +import scala.tools.asm.Opcodes +import scala.tools.asm.tree.ClassNode +import scala.tools.nsc.backend.jvm.CodeGenTools._ +import JavaConverters._ +import scala.tools.testing.ClearAfterClass + +object DefaultMethodTest extends ClearAfterClass.Clearable { + var compiler = newCompiler() + def clear(): Unit = { compiler = null } +} + +class DefaultMethodTest extends ClearAfterClass { + ClearAfterClass.stateToClear = DefaultMethodTest + val compiler = DefaultMethodTest.compiler + + @Test + def defaultMethodsViaGenBCode(): Unit = { + import compiler._ + val code = "package pack { trait T { def foo: Int }}" + object makeFooDefaultMethod extends Transformer { + val Foo = TermName("foo") + /** Transforms a single tree. */ + override def transform(tree: compiler.Tree): compiler.Tree = tree match { + case dd @ DefDef(_, Foo, _, _, _, _) => + dd.symbol.setFlag(reflect.internal.Flags.JAVA_DEFAULTMETHOD) + copyDefDef(dd)(rhs = Literal(Constant(1)).setType(definitions.IntTpe)) + case _ => super.transform(tree) + } + } + val asmClasses: List[ClassNode] = readAsmClasses(compileTransformed(compiler)(code, Nil, makeFooDefaultMethod.transform(_))) + val foo = asmClasses.head.methods.iterator.asScala.toList.last + assertTrue("default method should not be abstract", (foo.access & Opcodes.ACC_ABSTRACT) == 0) + assertTrue("default method body emitted", foo.instructions.size() > 0) + } + + +} diff --git a/test/junit/scala/tools/nsc/backend/jvm/DirectCompileTest.scala b/test/junit/scala/tools/nsc/backend/jvm/DirectCompileTest.scala index 240d3523f1..0cdc6ead10 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/DirectCompileTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/DirectCompileTest.scala @@ -10,7 +10,7 @@ import scala.tools.partest.ASMConverters._ import scala.tools.testing.ClearAfterClass object DirectCompileTest extends ClearAfterClass.Clearable { - var compiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:l:method") + var compiler = newCompiler(extraArgs = "-Yopt:l:method") def clear(): Unit = { compiler = null } } diff --git a/test/junit/scala/tools/nsc/backend/jvm/IndyLambdaTest.scala b/test/junit/scala/tools/nsc/backend/jvm/IndyLambdaTest.scala new file mode 100644 index 0000000000..d29f6b0a13 --- /dev/null +++ b/test/junit/scala/tools/nsc/backend/jvm/IndyLambdaTest.scala @@ -0,0 +1,74 @@ +package scala.tools.nsc.backend.jvm + +import org.junit.Assert._ +import org.junit.{Assert, Test} + +import scala.tools.asm.{Handle, Opcodes} +import scala.tools.asm.tree.InvokeDynamicInsnNode +import scala.tools.nsc.backend.jvm.AsmUtils._ +import scala.tools.nsc.backend.jvm.CodeGenTools._ +import scala.tools.testing.ClearAfterClass +import scala.collection.JavaConverters._ + +object IndyLambdaTest extends ClearAfterClass.Clearable { + var compiler = newCompiler() + + def clear(): Unit = { + compiler = null + } +} + +class IndyLambdaTest extends ClearAfterClass { + ClearAfterClass.stateToClear = IndyLambdaTest + val compiler = IndyLambdaTest.compiler + + @Test def boxingBridgeMethodUsedSelectively(): Unit = { + def implMethodDescriptorFor(code: String): String = { + val method = compileMethods(compiler)(s"""def f = $code """).find(_.name == "f").get + val x = method.instructions.iterator.asScala.toList + x.flatMap { + case insn : InvokeDynamicInsnNode => insn.bsmArgs.collect { case h : Handle => h.getDesc } + case _ => Nil + }.head + } + + val obj = "Ljava/lang/Object;" + val str = "Ljava/lang/String;" + + // unspecialized functions that have a primitive in parameter or return position + // give rise to a "boxing bridge" method (which has the suffix `$adapted`). + // This is because Scala's unboxing of null values gives zero, whereas Java's throw a NPE. + + // 1. Here we show that we are calling the boxing bridge (the lambda bodies here are compiled into + // methods of `(I)Ljava/lang/Object;` / `(I)Ljava/lang/Object;` respectively.) + assertEquals(s"($obj)$obj", implMethodDescriptorFor("(x: Int) => new Object")) + assertEquals(s"($obj)$obj", implMethodDescriptorFor("(x: Object) => 0")) + + // 2a. We don't need such adaptations for parameters or return values with types that differ + // from Object due to other generic substitution, LambdaMetafactory will downcast the arguments. + assertEquals(s"($str)$str", implMethodDescriptorFor("(x: String) => x")) + + // 2b. Testing 2a. in combination with 1. + assertEquals(s"($obj)$str", implMethodDescriptorFor("(x: Int) => \"\"")) + assertEquals(s"($str)$obj", implMethodDescriptorFor("(x: String) => 0")) + + // 3. Specialized functions, don't need any of this as they implement a method like `apply$mcII$sp`, + // and the (un)boxing is handled in the base class in code emitted by scalac. + assertEquals("(I)I", implMethodDescriptorFor("(x: Int) => x")) + + // non-builtin sams are like specialized functions + compileClasses(compiler)("class VC(private val i: Int) extends AnyVal; trait FunVC { def apply(a: VC): VC }") + assertEquals("(I)I", implMethodDescriptorFor("((x: VC) => x): FunVC")) + + compileClasses(compiler)("trait Fun1[T, U] { def apply(a: T): U }") + assertEquals(s"($obj)$str", implMethodDescriptorFor("(x => x.toString): Fun1[Int, String]")) + assertEquals(s"($obj)$obj", implMethodDescriptorFor("(x => println(x)): Fun1[Int, Unit]")) + assertEquals(s"($obj)$str", implMethodDescriptorFor("((x: VC) => \"\") : Fun1[VC, String]")) + assertEquals(s"($str)$obj", implMethodDescriptorFor("((x: String) => new VC(0)) : Fun1[String, VC]")) + + compileClasses(compiler)("trait Coll[A, Repr] extends Any") + compileClasses(compiler)("final class ofInt(val repr: Array[Int]) extends AnyVal with Coll[Int, Array[Int]]") + + assertEquals(s"([I)$obj", implMethodDescriptorFor("((xs: Array[Int]) => new ofInt(xs)): Array[Int] => Coll[Int, Array[Int]]")) + } +} diff --git a/test/junit/scala/tools/nsc/backend/jvm/IndySammyTest.scala b/test/junit/scala/tools/nsc/backend/jvm/IndySammyTest.scala new file mode 100644 index 0000000000..b9e45a7dc9 --- /dev/null +++ b/test/junit/scala/tools/nsc/backend/jvm/IndySammyTest.scala @@ -0,0 +1,160 @@ +package scala.tools.nsc +package backend.jvm + +import org.junit.Assert.assertEquals +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.junit.Test + +import scala.tools.asm.Opcodes._ +import scala.tools.asm.tree._ +import scala.tools.nsc.reporters.StoreReporter +import CodeGenTools._ +import scala.tools.partest.ASMConverters +import ASMConverters._ + +import scala.tools.testing.ClearAfterClass + +object IndySammyTest extends ClearAfterClass.Clearable { + var _compiler = newCompiler() + + def compile(scalaCode: String, javaCode: List[(String, String)] = Nil, allowMessage: StoreReporter#Info => Boolean = _ => false): List[ClassNode] = + compileClasses(_compiler)(scalaCode, javaCode, allowMessage) + + def clear(): Unit = { _compiler = null } +} + +@RunWith(classOf[JUnit4]) +class IndySammyTest extends ClearAfterClass { + ClearAfterClass.stateToClear = IndySammyTest + import IndySammyTest._ + + val compiler = _compiler + + def funClassName(from: String, to: String) = s"Fun$from$to" + def classPrologue(from: String, to: String) = + "class VC(private val i: Int) extends AnyVal\n" + + s"trait ${funClassName(from, to)} { def apply(a: $from): $to}" + + def lamDef(from: String, to: String, body: String => String) = + s"""def lam = (x => ${body("x")}): ${funClassName(from, to)}""" + + def appDef(arg: String) = s"""def app = lam($arg)""" + + /* Create a lambda of type "$from => $to" (with body "$body(x)" if "x" is the argument name), + * and apply it to `arg`. + * + * Check: + * - the signature of the apply method + * - the instructions in the lambda's body (anonfun method) + * - the instructions used to create the argument for the application + * (and the return corresponding to the lambda's result type) + */ + def test(from: String, to: String, arg: String, body: String => String = x => x) + (expectedSig: String, lamBody: List[Instruction], appArgs: List[Instruction], ret: Instruction) + (allowMessage: StoreReporter#Info => Boolean = _ => false) = { + val cls = compile(s"${classPrologue(from, to)}") + val methodNodes = compileMethods(compiler)(lamDef(from, to, body) +";"+ appDef(arg), allowMessage) + + val applySig = cls.head.methods.get(0).desc + val anonfun = methodNodes.find(_.name contains "$anonfun$").map(convertMethod).get + val lamInsn = methodNodes.find(_.name == "lam").map(instructionsFromMethod).get.dropNonOp + val applyInvoke = methodNodes.find(_.name == "app").map(convertMethod).get + + assertEquals(expectedSig, applySig) + assert(lamInsn.length == 2 && lamInsn.head.isInstanceOf[InvokeDynamic], lamInsn) + assertSameCode(anonfun, lamBody) + assertSameCode(applyInvoke, List( + VarOp(ALOAD, 0), + Invoke(INVOKEVIRTUAL, "C", "lam", s"()L${funClassName(from, to)};", false)) ++ appArgs ++ List( + Invoke(INVOKEINTERFACE, funClassName(from, to), "apply", applySig, true), ret) + ) + } + +// def testSpecial(lam: String, lamTp: String, arg: String)(allowMessage: StoreReporter#Info => Boolean = _ => false) = { +// val cls = compile("trait Special[@specialized A] { def apply(a: A): A}" ) +// val methodNodes = compileMethods(compiler)(s"def lam : $lamTp = $lam" +";"+ appDef(arg), allowMessage) +// +// val anonfun = methodNodes.filter(_.name contains "$anonfun$").map(convertMethod) +// val lamInsn = methodNodes.find(_.name == "lam").map(instructionsFromMethod).get.dropNonOp +// val applyInvoke = methodNodes.find(_.name == "app").map(convertMethod).get +// +// assert(lamInsn.length == 2 && lamInsn.head.isInstanceOf[InvokeDynamic], lamInsn) +// assertSameCode(anonfun, lamBody) +// assertSameCode(applyInvoke, List( +// VarOp(ALOAD, 0), +// Invoke(INVOKEVIRTUAL, "C", "lam", s"()L${funClassName(from, to)};", false)) ++ appArgs ++ List( +// Invoke(INVOKEINTERFACE, funClassName(from, to), "apply", applySig, true), ret) +// ) +// } + + // x => x : VC => VC applied to VC(1) + @Test + def testVC_VC_VC = + test("VC", "VC", "new VC(1)")("(I)I", + List(VarOp(ILOAD, 0), Op(IRETURN)), + List(Op(ICONST_1)), + Op(IRETURN))() + + // x => new VC(x) : Int => VC applied to 1 + @Test + def testInt_VC_1 = + test("Int", "VC", "1", x => s"new VC($x)")("(I)I", + List(VarOp(ILOAD, 0), Op(IRETURN)), + List(Op(ICONST_1)), + Op(IRETURN))() + + // x => x : VC => Int applied to VC(1) + @Test + def testVC_Int_VC = + test("VC", "Int", "new VC(1)", x => "1")("(I)I", + List(Op(ICONST_1), Op(IRETURN)), + List(Op(ICONST_1)), + Op(IRETURN))() + + // x => new VC(1) : VC => Any applied to VC(1) + @Test + def testVC_Any_VC = + test("VC", "Any", "new VC(1)", x => s"new VC(1)")("(I)Ljava/lang/Object;", + List(TypeOp(NEW, "VC"), Op(DUP), Op(ICONST_1), Invoke(INVOKESPECIAL, "VC", "<init>", "(I)V", false), Op(ARETURN)), + List(Op(ICONST_1)), + Op(ARETURN))() + + + // x => x : VC => Unit applied to VC(1) + @Test + def testVC_Unit_VC = + test("VC", "Unit", "new VC(1)")("(I)V", + List(VarOp(ILOAD, 0), Op(POP), Op(RETURN)), + List(Op(ICONST_1)), + Op(RETURN))(allowMessage = _.msg.contains("pure expression")) + + // x => new VC(x.asInstanceOf[Int]) : Any => VC applied to 1 + // + // Scala: + // def lam = (x => new VC(x.asInstanceOf[Int])): FunAny_VC + // def app = lam(1) + // Java: + // FunAny_VC lam() { return x -> BoxesRunTime.unboxToInt((Object)x); } + // int app() { lam().apply(BoxesRunTime.boxToInteger((int)1)); + @Test + def testAny_VC_1 = + test("Any", "VC", "1", x => s"new VC($x.asInstanceOf[Int])")("(Ljava/lang/Object;)I", + List(VarOp(ALOAD, 0), Invoke(INVOKESTATIC, "scala/runtime/BoxesRunTime", "unboxToInt", "(Ljava/lang/Object;)I", false), Op(IRETURN)), + List(Op(ICONST_1), Invoke(INVOKESTATIC, "scala/runtime/BoxesRunTime", "boxToInteger", "(I)Ljava/lang/Integer;", false)), + Op(IRETURN))() + + // TODO + // x => x : Special[Int] applied to 1 +// @Test +// def testSpecial_Int_1 = +// testSpecial("x => x", "Special[Int]", "1")() + + + // Tests ThisReferringMethodsTraverser + @Test + def testStaticIfNoThisReference: Unit = { + val methodNodes = compileMethods(compiler)("def foo = () => () => () => 42") + methodNodes.forall(m => !m.name.contains("anonfun") || (m.access & ACC_STATIC) == ACC_STATIC) + } +} diff --git a/test/junit/scala/tools/nsc/backend/jvm/StringConcatTest.scala b/test/junit/scala/tools/nsc/backend/jvm/StringConcatTest.scala new file mode 100644 index 0000000000..2a9b8f7198 --- /dev/null +++ b/test/junit/scala/tools/nsc/backend/jvm/StringConcatTest.scala @@ -0,0 +1,133 @@ +package scala.tools.nsc +package backend.jvm + +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.testing.AssertUtil._ + +import CodeGenTools._ +import scala.tools.partest.ASMConverters +import ASMConverters._ +import scala.tools.testing.ClearAfterClass + +object StringConcatTest extends ClearAfterClass.Clearable { + var compiler = newCompiler() + def clear(): Unit = { compiler = null } +} + +@RunWith(classOf[JUnit4]) +class StringConcatTest extends ClearAfterClass { + ClearAfterClass.stateToClear = StringConcatTest + val compiler = StringConcatTest.compiler + + @Test + def appendOverloadNoBoxing(): Unit = { + val code = + """class C { + | def t1( + | v: Unit, + | z: Boolean, + | c: Char, + | b: Byte, + | s: Short, + | i: Int, + | l: Long, + | f: Float, + | d: Double, + | str: String, + | sbuf: java.lang.StringBuffer, + | chsq: java.lang.CharSequence, + | chrs: Array[Char]) = str + this + v + z + c + b + s + i + f + l + d + sbuf + chsq + chrs + | + | // similar, but starting off with any2stringadd + | def t2( + | v: Unit, + | z: Boolean, + | c: Char, + | b: Byte, + | s: Short, + | i: Int, + | l: Long, + | f: Float, + | d: Double, + | str: String, + | sbuf: java.lang.StringBuffer, + | chsq: java.lang.CharSequence, + | chrs: Array[Char]) = this + str + v + z + c + b + s + i + f + l + d + sbuf + chsq + chrs + |} + """.stripMargin + val List(c) = compileClasses(compiler)(code) + + def invokeNameDesc(m: String): List[String] = getSingleMethod(c, m).instructions collect { + case Invoke(_, _, name, desc, _) => name + desc + } + assertEquals(invokeNameDesc("t1"), List( + "<init>()V", + "append(Ljava/lang/String;)Ljava/lang/StringBuilder;", + "append(Ljava/lang/Object;)Ljava/lang/StringBuilder;", + "append(Ljava/lang/Object;)Ljava/lang/StringBuilder;", + "append(Z)Ljava/lang/StringBuilder;", + "append(C)Ljava/lang/StringBuilder;", + "append(I)Ljava/lang/StringBuilder;", + "append(I)Ljava/lang/StringBuilder;", + "append(I)Ljava/lang/StringBuilder;", + "append(F)Ljava/lang/StringBuilder;", + "append(J)Ljava/lang/StringBuilder;", + "append(D)Ljava/lang/StringBuilder;", + "append(Ljava/lang/StringBuffer;)Ljava/lang/StringBuilder;", + "append(Ljava/lang/CharSequence;)Ljava/lang/StringBuilder;", + "append(Ljava/lang/Object;)Ljava/lang/StringBuilder;", // test that we're not using the [C overload + "toString()Ljava/lang/String;")) + + assertEquals(invokeNameDesc("t2"), List( + "<init>()V", + "any2stringadd(Ljava/lang/Object;)Ljava/lang/Object;", + "$plus$extension(Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/String;", + "append(Ljava/lang/String;)Ljava/lang/StringBuilder;", + "append(Ljava/lang/Object;)Ljava/lang/StringBuilder;", + "append(Z)Ljava/lang/StringBuilder;", + "append(C)Ljava/lang/StringBuilder;", + "append(I)Ljava/lang/StringBuilder;", + "append(I)Ljava/lang/StringBuilder;", + "append(I)Ljava/lang/StringBuilder;", + "append(F)Ljava/lang/StringBuilder;", + "append(J)Ljava/lang/StringBuilder;", + "append(D)Ljava/lang/StringBuilder;", + "append(Ljava/lang/StringBuffer;)Ljava/lang/StringBuilder;", + "append(Ljava/lang/CharSequence;)Ljava/lang/StringBuilder;", + "append(Ljava/lang/Object;)Ljava/lang/StringBuilder;", // test that we're not using the [C overload + "toString()Ljava/lang/String;")) + } + + @Test + def concatPrimitiveCorrectness(): Unit = { + val obj: Object = new { override def toString = "TTT" } + def t( + v: Unit, + z: Boolean, + c: Char, + b: Byte, + s: Short, + i: Int, + l: Long, + f: Float, + d: Double, + str: String, + sbuf: java.lang.StringBuffer, + chsq: java.lang.CharSequence, + chrs: Array[Char]) = { + val s1 = str + obj + v + z + c + b + s + i + f + l + d + sbuf + chsq + chrs + val s2 = obj + str + v + z + c + b + s + i + f + l + d + sbuf + chsq + chrs + s1 + "//" + s2 + } + def sbuf = { val r = new java.lang.StringBuffer(); r.append("sbuf"); r } + def chsq: java.lang.CharSequence = "chsq" + val s = t((), true, 'd', 3: Byte, 12: Short, 3, -32l, 12.3f, -4.2d, "me", sbuf, chsq, Array('a', 'b')) + val r = s.replaceAll("""\[C@\w+""", "<ARRAY>") + assertEquals(r, "meTTT()trued312312.3-32-4.2sbufchsq<ARRAY>//TTTme()trued312312.3-32-4.2sbufchsq<ARRAY>") + } +} 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 94e776aadb..571d84c872 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/analysis/NullnessAnalyzerTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/analysis/NullnessAnalyzerTest.scala @@ -17,10 +17,10 @@ import scala.tools.testing.ClearAfterClass import scala.tools.nsc.backend.jvm.opt.BytecodeUtils._ import AsmUtils._ -import scala.collection.convert.decorateAsScala._ +import scala.collection.JavaConverters._ object NullnessAnalyzerTest extends ClearAfterClass.Clearable { - var noOptCompiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:l:none") + var noOptCompiler = newCompiler(extraArgs = "-Yopt:l:none") def clear(): Unit = { noOptCompiler = null @@ -31,25 +31,22 @@ object NullnessAnalyzerTest extends ClearAfterClass.Clearable { class NullnessAnalyzerTest extends ClearAfterClass { ClearAfterClass.stateToClear = NullnessAnalyzerTest val noOptCompiler = NullnessAnalyzerTest.noOptCompiler + import noOptCompiler.genBCode.bTypes.backendUtils._ - def newNullnessAnalyzer(methodNode: MethodNode, classInternalName: InternalName = "C"): NullnessAnalyzer = { - val nullnessAnalyzer = new NullnessAnalyzer - nullnessAnalyzer.analyze(classInternalName, methodNode) - nullnessAnalyzer - } + def newNullnessAnalyzer(methodNode: MethodNode, classInternalName: InternalName = "C") = new AsmAnalyzer(methodNode, classInternalName, new NullnessAnalyzer(noOptCompiler.genBCode.bTypes)) - def testNullness(analyzer: NullnessAnalyzer, method: MethodNode, query: String, index: Int, nullness: Nullness): Unit = { + def testNullness(analyzer: AsmAnalyzer[NullnessValue], method: MethodNode, query: String, index: Int, nullness: NullnessValue): Unit = { for (i <- findInstr(method, query)) { - val r = analyzer.frameAt(i, method).getValue(index).nullness + val r = analyzer.frameAt(i).getValue(index) assertTrue(s"Expected: $nullness, found: $r. At instr ${textify(i)}", nullness == r) } } // debug / helper for writing tests - def showAllNullnessFrames(analyzer: NullnessAnalyzer, method: MethodNode): String = { + def showAllNullnessFrames(analyzer: AsmAnalyzer[NullnessValue], method: MethodNode): String = { val instrLength = method.instructions.iterator.asScala.map(textify(_).length).max val lines = for (i <- method.instructions.iterator.asScala) yield { - val f = analyzer.frameAt(i, method) + val f = analyzer.frameAt(i) val frameString = { if (f == null) "null" else (0 until (f.getLocals + f.getStackSize)).iterator @@ -71,12 +68,13 @@ 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) } @@ -84,15 +82,15 @@ class NullnessAnalyzerTest extends ClearAfterClass { def thisNonNull(): Unit = { val List(m) = compileMethods(noOptCompiler)("def f = this.toString") val a = newNullnessAnalyzer(m) - testNullness(a, m, "ALOAD 0", 0, NotNull) + testNullness(a, m, "ALOAD 0", 0, NotNullValue) } @Test def instanceMethodCall(): Unit = { val List(m) = compileMethods(noOptCompiler)("def f(a: String) = a.trim") val a = newNullnessAnalyzer(m) - testNullness(a, m, "INVOKEVIRTUAL java/lang/String.trim", 1, Unknown) - testNullness(a, m, "ARETURN", 1, NotNull) + testNullness(a, m, "INVOKEVIRTUAL java/lang/String.trim", 1, UnknownValue1) + testNullness(a, m, "ARETURN", 1, NotNullValue) } @Test @@ -110,13 +108,13 @@ class NullnessAnalyzerTest extends ClearAfterClass { // ARETURN: 0: NotNull, 1: NotNull, 2: Unknown for ((insn, index, nullness) <- List( - ("+NEW", 2, Unknown), // new value at slot 2 on the stack - ("+DUP", 3, Unknown), - ("+INVOKESPECIAL java/lang/Object", 2, NotNull), // after calling the initializer on 3, the value at 2 becomes NotNull - ("ASTORE 1", 1, Unknown), // before the ASTORE 1, nullness of the value in local 1 is Unknown - ("+ASTORE 1", 1, NotNull), // after storing the value at 2 in local 1, the local 1 is NotNull - ("+ALOAD 1", 2, NotNull), // loading the value 1 puts a NotNull value on the stack (at 2) - ("+INVOKEVIRTUAL java/lang/Object.toString", 2, Unknown) // nullness of value returned by `toString` is Unknown + ("+NEW", 2, UnknownValue1), // new value at slot 2 on the stack + ("+DUP", 3, UnknownValue1), + ("+INVOKESPECIAL java/lang/Object", 2, NotNullValue), // after calling the initializer on 3, the value at 2 becomes NotNull + ("ASTORE 1", 1, UnknownValue1), // before the ASTORE 1, nullness of the value in local 1 is Unknown + ("+ASTORE 1", 1, NotNullValue), // after storing the value at 2 in local 1, the local 1 is NotNull + ("+ALOAD 1", 2, NotNullValue), // loading the value 1 puts a NotNull value on the stack (at 2) + ("+INVOKEVIRTUAL java/lang/Object.toString", 2, UnknownValue1) // nullness of value returned by `toString` is Unknown )) testNullness(a, m, insn, index, nullness) } @@ -125,9 +123,9 @@ class NullnessAnalyzerTest extends ClearAfterClass { val List(m) = compileMethods(noOptCompiler)("def f = { var a: Object = null; a }") val a = newNullnessAnalyzer(m) for ((insn, index, nullness) <- List( - ("+ACONST_NULL", 2, Null), - ("+ASTORE 1", 1, Null), - ("+ALOAD 1", 2, Null) + ("+ACONST_NULL", 2, NullValue), + ("+ASTORE 1", 1, NullValue), + ("+ALOAD 1", 2, NullValue) )) testNullness(a, m, insn, index, nullness) } @@ -135,15 +133,33 @@ class NullnessAnalyzerTest extends ClearAfterClass { def stringLiteralsNotNull(): Unit = { val List(m) = compileMethods(noOptCompiler)("""def f = { val a = "hi"; a.trim }""") val a = newNullnessAnalyzer(m) - testNullness(a, m, "+ASTORE 1", 1, NotNull) + testNullness(a, m, "+ASTORE 1", 1, NotNullValue) } @Test def newArraynotNull() { val List(m) = compileMethods(noOptCompiler)("def f = { val a = new Array[Int](2); a(0) }") val a = newNullnessAnalyzer(m) - testNullness(a, m, "+NEWARRAY T_INT", 2, NotNull) // new array on stack - testNullness(a, m, "+ASTORE 1", 1, NotNull) // local var (a) + testNullness(a, m, "+NEWARRAY T_INT", 2, NotNullValue) // new array on stack + testNullness(a, m, "+ASTORE 1", 1, NotNullValue) // local var (a) + } + + @Test + def mergeNullNotNull(): Unit = { + val code = + """def f(o: Object) = { + | var a: Object = o + | var c: Object = null + | if ("".trim eq "-") { + | c = o + | } + | a.toString + |} + """.stripMargin + val List(m) = compileMethods(noOptCompiler)(code) + val a = newNullnessAnalyzer(m) + val toSt = "+INVOKEVIRTUAL java/lang/Object.toString" + testNullness(a, m, toSt, 3, UnknownValue1) } @Test @@ -173,22 +189,22 @@ class NullnessAnalyzerTest extends ClearAfterClass { val toSt = "INVOKEVIRTUAL java/lang/Object.toString" val end = s"+$toSt" for ((insn, index, nullness) <- List( - (trim, 0, NotNull), // this - (trim, 1, Unknown), // parameter o - (trim, 2, Unknown), // a - (trim, 3, Null), // b - (trim, 4, Null), // c - (trim, 5, Unknown), // d - - (toSt, 2, Unknown), // a, still the same - (toSt, 3, Unknown), // b, was re-assinged in both branches to Unknown - (toSt, 4, Unknown), // c, was re-assigned in one branch to Unknown - (toSt, 5, Null), // d, was assigned to null in both branches - - (end, 2, NotNull), // a, NotNull (alias of b) - (end, 3, NotNull), // b, receiver of toString - (end, 4, Unknown), // c, no change (not an alias of b) - (end, 5, Null) // d, no change + (trim, 0, NotNullValue), // this + (trim, 1, UnknownValue1), // parameter o + (trim, 2, UnknownValue1), // a + (trim, 3, NullValue), // b + (trim, 4, NullValue), // c + (trim, 5, UnknownValue1), // d + + (toSt, 2, UnknownValue1), // a, still the same + (toSt, 3, UnknownValue1), // b, was re-assinged in both branches to Unknown + (toSt, 4, UnknownValue1), // c, was re-assigned in one branch to Unknown + (toSt, 5, NullValue), // d, was assigned to null in both branches + + (end, 2, NotNullValue), // a, NotNull (alias of b) + (end, 3, NotNullValue), // b, receiver of toString + (end, 4, UnknownValue1), // c, no change (not an alias of b) + (end, 5, NullValue) // d, no change )) testNullness(a, m, insn, index, nullness) } @@ -210,11 +226,11 @@ class NullnessAnalyzerTest extends ClearAfterClass { val trim = "INVOKEVIRTUAL java/lang/String.trim" for ((insn, index, nullness) <- List( - (instof, 1, Unknown), // a after INSTANCEOF - (instof, 2, Unknown), // x after INSTANCEOF - (tost, 1, NotNull), - (tost, 2, NotNull), - (trim, 3, NotNull) // receiver at `trim` + (instof, 1, UnknownValue1), // a after INSTANCEOF + (instof, 2, UnknownValue1), // x after INSTANCEOF + (tost, 1, NotNullValue), + (tost, 2, NotNullValue), + (trim, 3, NotNullValue) // receiver at `trim` )) testNullness(a, m, insn, index, nullness) } } diff --git a/test/junit/scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzerTest.scala b/test/junit/scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzerTest.scala index 941a167114..d54b8ac563 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzerTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzerTest.scala @@ -15,7 +15,7 @@ import CodeGenTools._ import AsmUtils._ object ProdConsAnalyzerTest extends ClearAfterClass.Clearable { - var noOptCompiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:l:none") + var noOptCompiler = newCompiler(extraArgs = "-Yopt:l:none") def clear(): Unit = { noOptCompiler = null @@ -26,6 +26,7 @@ object ProdConsAnalyzerTest extends ClearAfterClass.Clearable { class ProdConsAnalyzerTest extends ClearAfterClass { ClearAfterClass.stateToClear = ProdConsAnalyzerTest val noOptCompiler = ProdConsAnalyzerTest.noOptCompiler + import noOptCompiler.genBCode.bTypes.backendUtils._ def prodToString(producer: AbstractInsnNode) = producer match { case p: InitialProducer => p.toString diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/AnalyzerTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/AnalyzerTest.scala new file mode 100644 index 0000000000..930f7f2f10 --- /dev/null +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/AnalyzerTest.scala @@ -0,0 +1,63 @@ +package scala.tools.nsc +package backend.jvm +package opt + +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.junit.Test +import org.junit.Assert._ + +import scala.tools.asm.tree._ +import scala.tools.asm.tree.analysis._ +import scala.tools.nsc.backend.jvm.analysis.{AliasingFrame, AliasingAnalyzer} + +import CodeGenTools._ +import scala.tools.partest.ASMConverters +import ASMConverters._ +import AsmUtils._ +import BackendReporting._ +import BytecodeUtils._ + +import scala.collection.JavaConverters._ +import scala.tools.testing.ClearAfterClass + +object AnalyzerTest extends ClearAfterClass.Clearable { + var noOptCompiler = newCompiler(extraArgs = "-Yopt:l:none") + def clear(): Unit = { noOptCompiler = null } +} + +@RunWith(classOf[JUnit4]) +class AnalyzerTest extends ClearAfterClass { + ClearAfterClass.stateToClear = AnalyzerTest + val noOptCompiler = AnalyzerTest.noOptCompiler + + @Test + def aliasingOfPrimitives(): Unit = { + val code = + """class C { + | def f(a: Int, b: Long) = { + | val c = a - b // a is converted with i2l + | val d = c + | val e = a + | // locals: 0 this -- 1 a -- 2-3 b -- 4-5 c -- 6-7 d -- 8 e + | e + d // e is converted with i2l + | } + |} + """.stripMargin + + val List(c) = compileClasses(noOptCompiler)(code) + val a = new AliasingAnalyzer(new BasicInterpreter) + val f = findAsmMethod(c, "f") + a.analyze("C", f) + + val List(_, i2l) = findInstr(f, "I2L") + val aliasesAtI2l = a.frameAt(i2l, f).asInstanceOf[AliasingFrame[_]].aliases + assertEquals(aliasesAtI2l(1).iterator.toList, List(1, 8, 9)) // a, e and stack top + assertEquals(aliasesAtI2l(4).iterator.toList, List(4, 6)) + + val List(add) = findInstr(f, "LADD") + val aliasesAtAdd = a.frameAt(add, f).asInstanceOf[AliasingFrame[_]].aliases + assertEquals(aliasesAtAdd(1).iterator.toList, List(1, 8)) // after i2l the value on the stack is no longer an alias + assertEquals(aliasesAtAdd(4).iterator.toList, List(4, 6, 10)) // c, d and stack top + } +} 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 1b6c080234..aba0aab038 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala @@ -17,12 +17,12 @@ import ASMConverters._ import BackendReporting._ -import scala.collection.convert.decorateAsScala._ +import scala.collection.JavaConverters._ @RunWith(classOf[JUnit4]) class BTypesFromClassfileTest { // inliner enabled -> inlineInfos are collected (and compared) in ClassBTypes - val compiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:inline-global") + val compiler = newCompiler(extraArgs = "-Yopt:inline-global") import compiler._ import definitions._ diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala index 9fda034a04..1d30e42e3c 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala @@ -6,6 +6,7 @@ import org.junit.runner.RunWith import org.junit.runners.JUnit4 import org.junit.Test import scala.collection.generic.Clearable +import scala.collection.immutable.IntMap import scala.tools.asm.Opcodes._ import org.junit.Assert._ @@ -20,26 +21,57 @@ import ASMConverters._ import AsmUtils._ import BackendReporting._ -import scala.collection.convert.decorateAsScala._ +import scala.collection.JavaConverters._ +import scala.tools.testing.ClearAfterClass -@RunWith(classOf[JUnit4]) -class CallGraphTest { - val compiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:inline-global -Yopt-warnings") - import compiler.genBCode.bTypes._ +object CallGraphTest extends ClearAfterClass.Clearable { + var compiler = newCompiler(extraArgs = "-Yopt:inline-global -Yopt-warnings") + def clear(): Unit = { compiler = null } // allows inspecting the caches after a compilation run - val notPerRun: List[Clearable] = List(classBTypeFromInternalName, byteCodeRepository.classes, callGraph.callsites) + val notPerRun: List[Clearable] = List( + compiler.genBCode.bTypes.classBTypeFromInternalName, + compiler.genBCode.bTypes.byteCodeRepository.compilingClasses, + compiler.genBCode.bTypes.byteCodeRepository.parsedClasses, + compiler.genBCode.bTypes.callGraph.callsites) notPerRun foreach compiler.perRunCaches.unrecordCache +} + +@RunWith(classOf[JUnit4]) +class CallGraphTest extends ClearAfterClass { + ClearAfterClass.stateToClear = CallGraphTest + + val compiler = CallGraphTest.compiler + import compiler.genBCode.bTypes._ + import callGraph._ - def compile(code: String, allowMessage: StoreReporter#Info => Boolean): List[ClassNode] = { - notPerRun.foreach(_.clear()) - compileClasses(compiler)(code, allowMessage = allowMessage) + def compile(code: String, allowMessage: StoreReporter#Info => Boolean = _ => false): List[ClassNode] = { + CallGraphTest.notPerRun.foreach(_.clear()) + compileClasses(compiler)(code, allowMessage = allowMessage).map(c => byteCodeRepository.classNode(c.name).get) } def callsInMethod(methodNode: MethodNode): List[MethodInsnNode] = methodNode.instructions.iterator.asScala.collect({ case call: MethodInsnNode => call }).toList + def checkCallsite(call: MethodInsnNode, callsiteMethod: MethodNode, target: MethodNode, calleeDeclClass: ClassBType, + safeToInline: Boolean, atInline: Boolean, atNoInline: Boolean, argInfos: IntMap[ArgInfo] = IntMap.empty) = { + val callsite = callGraph.callsites(callsiteMethod)(call) + try { + assert(callsite.callsiteInstruction == call) + assert(callsite.callsiteMethod == callsiteMethod) + val callee = callsite.callee.get + assert(callee.callee == target) + assert(callee.calleeDeclarationClass == calleeDeclClass) + assert(callee.safeToInline == safeToInline) + assert(callee.annotatedInline == atInline) + assert(callee.annotatedNoInline == atNoInline) + assert(callsite.argInfos == argInfos) + } catch { + case e: Throwable => println(callsite); throw e + } + } + @Test def callGraphStructure(): Unit = { val code = @@ -83,70 +115,107 @@ class CallGraphTest { msgCount += 1 ok exists (m.msg contains _) } - val List(cCls, cMod, dCls, testCls) = compile(code, checkMsg).map(c => byteCodeRepository.classNode(c.name).get) + val List(cCls, cMod, dCls, testCls) = compile(code, checkMsg) assert(msgCount == 6, msgCount) - val List(cf1, cf2, cf3, cf4, cf5, cf6, cf7) = cCls.methods.iterator.asScala.filter(_.name.startsWith("f")).toList.sortBy(_.name) - val List(df1, df3) = dCls.methods.iterator.asScala.filter(_.name.startsWith("f")).toList.sortBy(_.name) - val g1 = cMod.methods.iterator.asScala.find(_.name == "g1").get - val List(t1, t2) = testCls.methods.iterator.asScala.filter(_.name.startsWith("t")).toList.sortBy(_.name) + val List(cf1, cf2, cf3, cf4, cf5, cf6, cf7) = findAsmMethods(cCls, _.startsWith("f")) + val List(df1, df3) = findAsmMethods(dCls, _.startsWith("f")) + val g1 = findAsmMethod(cMod, "g1") + val List(t1, t2) = findAsmMethods(testCls, _.startsWith("t")) val List(cf1Call, cf2Call, cf3Call, cf4Call, cf5Call, cf6Call, cf7Call, cg1Call) = callsInMethod(t1) val List(df1Call, df2Call, df3Call, df4Call, df5Call, df6Call, df7Call, dg1Call) = callsInMethod(t2) - def checkCallsite(callsite: callGraph.Callsite, - call: MethodInsnNode, callsiteMethod: MethodNode, target: MethodNode, calleeDeclClass: ClassBType, - safeToInline: Boolean, atInline: Boolean, atNoInline: Boolean) = try { - assert(callsite.callsiteInstruction == call) - assert(callsite.callsiteMethod == callsiteMethod) - val callee = callsite.callee.get - assert(callee.callee == target) - assert(callee.calleeDeclarationClass == calleeDeclClass) - assert(callee.safeToInline == safeToInline) - assert(callee.annotatedInline == atInline) - assert(callee.annotatedNoInline == atNoInline) - - assert(callsite.argInfos == List()) // not defined yet - } catch { - case e: Throwable => println(callsite); throw e - } - val cClassBType = classBTypeFromClassNode(cCls) val cMClassBType = classBTypeFromClassNode(cMod) val dClassBType = classBTypeFromClassNode(dCls) - checkCallsite(callGraph.callsites(cf1Call), - cf1Call, t1, cf1, cClassBType, false, false, false) - checkCallsite(callGraph.callsites(cf2Call), - cf2Call, t1, cf2, cClassBType, true, false, false) - checkCallsite(callGraph.callsites(cf3Call), - cf3Call, t1, cf3, cClassBType, false, true, false) - checkCallsite(callGraph.callsites(cf4Call), - cf4Call, t1, cf4, cClassBType, true, true, false) - checkCallsite(callGraph.callsites(cf5Call), - cf5Call, t1, cf5, cClassBType, false, false, true) - checkCallsite(callGraph.callsites(cf6Call), - cf6Call, t1, cf6, cClassBType, true, false, true) - checkCallsite(callGraph.callsites(cf7Call), - cf7Call, t1, cf7, cClassBType, false, true, true) - checkCallsite(callGraph.callsites(cg1Call), - cg1Call, t1, g1, cMClassBType, true, false, false) - - checkCallsite(callGraph.callsites(df1Call), - df1Call, t2, df1, dClassBType, false, true, false) - checkCallsite(callGraph.callsites(df2Call), - df2Call, t2, cf2, cClassBType, true, false, false) - checkCallsite(callGraph.callsites(df3Call), - df3Call, t2, df3, dClassBType, true, false, false) - checkCallsite(callGraph.callsites(df4Call), - df4Call, t2, cf4, cClassBType, true, true, false) - checkCallsite(callGraph.callsites(df5Call), - df5Call, t2, cf5, cClassBType, false, false, true) - checkCallsite(callGraph.callsites(df6Call), - df6Call, t2, cf6, cClassBType, true, false, true) - checkCallsite(callGraph.callsites(df7Call), - df7Call, t2, cf7, cClassBType, false, true, true) - checkCallsite(callGraph.callsites(dg1Call), - dg1Call, t2, g1, cMClassBType, true, false, false) + checkCallsite(cf1Call, t1, cf1, cClassBType, false, false, false) + checkCallsite(cf2Call, t1, cf2, cClassBType, true, false, false) + checkCallsite(cf3Call, t1, cf3, cClassBType, false, true, false) + checkCallsite(cf4Call, t1, cf4, cClassBType, true, true, false) + checkCallsite(cf5Call, t1, cf5, cClassBType, false, false, true) + checkCallsite(cf6Call, t1, cf6, cClassBType, true, false, true) + checkCallsite(cf7Call, t1, cf7, cClassBType, false, true, true) + checkCallsite(cg1Call, t1, g1, cMClassBType, true, false, false) + + checkCallsite(df1Call, t2, df1, dClassBType, false, true, false) + checkCallsite(df2Call, t2, cf2, cClassBType, true, false, false) + checkCallsite(df3Call, t2, df3, dClassBType, true, false, false) + checkCallsite(df4Call, t2, cf4, cClassBType, true, true, false) + checkCallsite(df5Call, t2, cf5, cClassBType, false, false, true) + checkCallsite(df6Call, t2, cf6, cClassBType, true, false, true) + checkCallsite(df7Call, t2, cf7, cClassBType, false, true, true) + checkCallsite(dg1Call, t2, g1, cMClassBType, true, false, false) + } + + @Test + def callerSensitiveNotSafeToInline(): Unit = { + val code = + """class C { + | def m = java.lang.Class.forName("C") + |} + """.stripMargin + val List(c) = compile(code) + val m = findAsmMethod(c, "m") + val List(fn) = callsInMethod(m) + val forNameMeth = byteCodeRepository.methodNode("java/lang/Class", "forName", "(Ljava/lang/String;)Ljava/lang/Class;").get._1 + val classTp = classBTypeFromInternalName("java/lang/Class") + val r = callGraph.callsites(m)(fn) + checkCallsite(fn, m, forNameMeth, classTp, safeToInline = false, atInline = false, atNoInline = false) + } + + @Test + def checkArgInfos(): Unit = { + val code = + """abstract class C { + | def h(f: Int => Int): Int = f(1) + | def t1 = h(x => x + 1) + | def t2(i: Int, f: Int => Int, z: Int) = h(f) + i - z + | def t3(f: Int => Int) = h(x => f(x + 1)) + |} + |trait D { + | def iAmASam(x: Int): Int + | def selfSamCall = iAmASam(10) + |} + |""".stripMargin + val List(c, d) = compile(code) + + def callIn(m: String) = callGraph.callsites.find(_._1.name == m).get._2.values.head + val t1h = callIn("t1") + assertEquals(t1h.argInfos.toList, List((1, FunctionLiteral))) + + val t2h = callIn("t2") + assertEquals(t2h.argInfos.toList, List((1, ForwardedParam(2)))) + + val t3h = callIn("t3") + assertEquals(t3h.argInfos.toList, List((1, FunctionLiteral))) + + val selfSamCall = callIn("selfSamCall") + assertEquals(selfSamCall.argInfos.toList, List((0,ForwardedParam(0)))) + } + + @Test + def argInfoAfterInlining(): Unit = { + val code = + """class C { + | def foo(f: Int => Int) = f(1) // not inlined + | @inline final def bar(g: Int => Int) = foo(g) // forwarded param 1 + | @inline final def baz = foo(x => x + 1) // literal + | + | def t1 = bar(x => x + 1) // call to foo should have argInfo literal + | def t2(x: Int, f: Int => Int) = x + bar(f) // call to foo should have argInfo forwarded param 2 + | def t3 = baz // call to foo should have argInfo literal + | def someFun: Int => Int = null + | def t4(x: Int) = x + bar(someFun) // call to foo has empty argInfo + |} + """.stripMargin + + compile(code) + def callIn(m: String) = callGraph.callsites.find(_._1.name == m).get._2.values.head + assertEquals(callIn("t1").argInfos.toList, List((1, FunctionLiteral))) + assertEquals(callIn("t2").argInfos.toList, List((1, ForwardedParam(2)))) + assertEquals(callIn("t3").argInfos.toList, List((1, FunctionLiteral))) + assertEquals(callIn("t4").argInfos.toList, Nil) } } diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/ClosureOptimizerTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/ClosureOptimizerTest.scala new file mode 100644 index 0000000000..12bfba71a8 --- /dev/null +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/ClosureOptimizerTest.scala @@ -0,0 +1,108 @@ +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.collection.generic.Clearable +import scala.collection.mutable.ListBuffer +import scala.reflect.internal.util.BatchSourceFile +import scala.tools.asm.Opcodes._ +import org.junit.Assert._ + +import scala.tools.asm.tree._ +import scala.tools.asm.tree.analysis._ +import scala.tools.nsc.io._ +import scala.tools.nsc.reporters.StoreReporter +import scala.tools.testing.AssertUtil._ + +import CodeGenTools._ +import scala.tools.partest.ASMConverters +import ASMConverters._ +import AsmUtils._ + +import BackendReporting._ + +import scala.collection.JavaConverters._ +import scala.tools.testing.ClearAfterClass + +object ClosureOptimizerTest extends ClearAfterClass.Clearable { + var compiler = newCompiler(extraArgs = "-Yopt:l:classpath -Yopt-warnings:_") + def clear(): Unit = { compiler = null } +} + +@RunWith(classOf[JUnit4]) +class ClosureOptimizerTest extends ClearAfterClass { + ClearAfterClass.stateToClear = ClosureOptimizerTest + + val compiler = ClosureOptimizerTest.compiler + + @Test + def nothingTypedClosureBody(): Unit = { + val code = + """abstract class C { + | def isEmpty: Boolean + | @inline final def getOrElse[T >: C](f: => T) = if (isEmpty) f else this + | def t = getOrElse(throw new Error("")) + |} + """.stripMargin + + val List(c) = compileClasses(compiler)(code) + val t = findAsmMethod(c, "t") + val List(bodyCall) = findInstr(t, "INVOKESTATIC C.C$$$anonfun$1 ()Lscala/runtime/Nothing$") + assert(bodyCall.getNext.getOpcode == ATHROW) + } + + @Test + def nullTypedClosureBody(): Unit = { + val code = + """abstract class C { + | def isEmpty: Boolean + | @inline final def getOrElse[T >: C](f: => T) = if (isEmpty) f else this + | def t = getOrElse(null) + |} + """.stripMargin + + val List(c) = compileClasses(compiler)(code) + val t = findAsmMethod(c, "t") + val List(bodyCall) = findInstr(t, "INVOKESTATIC C.C$$$anonfun$1 ()Lscala/runtime/Null$") + assert(bodyCall.getNext.getOpcode == POP) + assert(bodyCall.getNext.getNext.getOpcode == ACONST_NULL) + } + + @Test + def makeLMFCastExplicit(): Unit = { + val code = + """class C { + | def t(l: List[String]) = { + | val fun: String => String = s => s + | fun(l.head) + | } + |} + """.stripMargin + val List(c) = compileClasses(compiler)(code) + assertSameCode(getSingleMethod(c, "t"), + List(VarOp(ALOAD, 1), Invoke(INVOKEVIRTUAL, "scala/collection/immutable/List", "head", "()Ljava/lang/Object;", false), + TypeOp(CHECKCAST, "java/lang/String"), Invoke(INVOKESTATIC, "C", "C$$$anonfun$1", "(Ljava/lang/String;)Ljava/lang/String;", false), + Op(ARETURN))) + } + + @Test + def closureOptWithUnreachableCode(): Unit = { + // this example used to crash the ProdCons analysis in the closure optimizer - ProdCons + // expects no unreachable code. + val code = + """class C { + | @inline final def m = throw new Error("") + | def t = { + | val f = (x: Int) => x + 1 + | m + | f(10) // unreachable after inlining m + | } + |} + """.stripMargin + val List(c) = compileClasses(compiler)(code) + assertSameSummary(getSingleMethod(c, "t"), List(NEW, DUP, LDC, "<init>", ATHROW)) + } +} diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/CompactLocalVariablesTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/CompactLocalVariablesTest.scala index 76492cfa23..ac1b759fe2 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/CompactLocalVariablesTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/CompactLocalVariablesTest.scala @@ -17,8 +17,8 @@ class CompactLocalVariablesTest { // recurse-unreachable-jumps is required for eliminating catch blocks, in the first dce round they // are still live.only after eliminating the empty handler the catch blocks become unreachable. - val methodOptCompiler = newCompiler(extraArgs = "-target:jvm-1.6 -Ybackend:GenBCode -Yopt:unreachable-code,compact-locals") - val noCompactVarsCompiler = newCompiler(extraArgs = "-target:jvm-1.6 -Ybackend:GenBCode -Yopt:unreachable-code") + val methodOptCompiler = newCompiler(extraArgs = "-Yopt:unreachable-code,compact-locals") + val noCompactVarsCompiler = newCompiler(extraArgs = "-Yopt:unreachable-code") @Test def compactUnused(): Unit = { diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/EmptyExceptionHandlersTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/EmptyExceptionHandlersTest.scala index cb01f3d164..22aed4207f 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/EmptyExceptionHandlersTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/EmptyExceptionHandlersTest.scala @@ -14,8 +14,8 @@ import ASMConverters._ import scala.tools.testing.ClearAfterClass object EmptyExceptionHandlersTest extends ClearAfterClass.Clearable { - var noOptCompiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:l:none") - var dceCompiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:unreachable-code") + var noOptCompiler = newCompiler(extraArgs = "-Yopt:l:none") + var dceCompiler = newCompiler(extraArgs = "-Yopt:unreachable-code") def clear(): Unit = { noOptCompiler = null dceCompiler = null diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/InlineInfoTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/InlineInfoTest.scala index 57088bdd2f..23386bb5ae 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/InlineInfoTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlineInfoTest.scala @@ -16,18 +16,23 @@ import scala.tools.testing.ClearAfterClass import BackendReporting._ -import scala.collection.convert.decorateAsScala._ +import scala.collection.JavaConverters._ object InlineInfoTest extends ClearAfterClass.Clearable { - var compiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:l:classpath") + var compiler = newCompiler(extraArgs = "-Yopt:l:classpath") def clear(): Unit = { compiler = null } - def notPerRun: List[Clearable] = List(compiler.genBCode.bTypes.classBTypeFromInternalName, compiler.genBCode.bTypes.byteCodeRepository.classes) + def notPerRun: List[Clearable] = List( + compiler.genBCode.bTypes.classBTypeFromInternalName, + compiler.genBCode.bTypes.byteCodeRepository.compilingClasses, + compiler.genBCode.bTypes.byteCodeRepository.parsedClasses) notPerRun foreach compiler.perRunCaches.unrecordCache } @RunWith(classOf[JUnit4]) -class InlineInfoTest { +class InlineInfoTest extends ClearAfterClass { + ClearAfterClass.stateToClear = InlineInfoTest + val compiler = InlineInfoTest.compiler def compile(code: String) = { @@ -55,6 +60,7 @@ class InlineInfoTest { |class C extends T with U """.stripMargin val classes = compile(code) + val fromSyms = classes.map(c => compiler.genBCode.bTypes.classBTypeFromInternalName(c.name).info.get.inlineInfo) val fromAttrs = classes.map(c => { diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/InlineWarningTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/InlineWarningTest.scala index 029caa995c..1597c75a7e 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/InlineWarningTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlineWarningTest.scala @@ -13,7 +13,6 @@ import org.junit.Assert._ import scala.tools.asm.tree._ import scala.tools.asm.tree.analysis._ -import scala.tools.nsc.backend.jvm.opt.BytecodeUtils.AsmAnalyzer import scala.tools.nsc.io._ import scala.tools.nsc.reporters.StoreReporter import scala.tools.testing.AssertUtil._ @@ -25,14 +24,15 @@ import AsmUtils._ import BackendReporting._ -import scala.collection.convert.decorateAsScala._ +import scala.collection.JavaConverters._ import scala.tools.testing.ClearAfterClass object InlineWarningTest extends ClearAfterClass.Clearable { - val argsNoWarn = "-Ybackend:GenBCode -Yopt:l:classpath" + val argsNoWarn = "-Yopt:l:classpath" val args = argsNoWarn + " -Yopt-warnings" var compiler = newCompiler(extraArgs = args) - def clear(): Unit = { compiler = null } + var compilerWarnAll = newCompiler(extraArgs = argsNoWarn + " -Yopt-warnings:_") + def clear(): Unit = { compiler = null; compilerWarnAll = null } } @RunWith(classOf[JUnit4]) @@ -40,8 +40,9 @@ class InlineWarningTest extends ClearAfterClass { ClearAfterClass.stateToClear = InlineWarningTest val compiler = InlineWarningTest.compiler + val compilerWarnAll = InlineWarningTest.compilerWarnAll - def compile(scalaCode: String, javaCode: List[(String, String)] = Nil, allowMessage: StoreReporter#Info => Boolean = _ => false): List[ClassNode] = { + def compile(scalaCode: String, javaCode: List[(String, String)] = Nil, allowMessage: StoreReporter#Info => Boolean = _ => false, compiler: Global = compiler): List[ClassNode] = { compileClasses(compiler)(scalaCode, javaCode, allowMessage) } @@ -70,29 +71,6 @@ class InlineWarningTest extends ClearAfterClass { } @Test - def traitMissingImplClass(): Unit = { - val codeA = "trait T { @inline final def f = 1 }" - val codeB = "class C { def t1(t: T) = t.f }" - - val removeImpl = (outDir: AbstractFile) => { - val f = outDir.lookupName("T$class.class", directory = false) - if (f != null) f.delete() - } - - val warn = - """T::f()I is annotated @inline but cannot be inlined: the trait method call could not be rewritten to the static implementation method. Possible reason: - |The method f(LT;)I could not be found in the class T$class or any of its parents. - |Note that the following parent classes could not be found on the classpath: T$class""".stripMargin - - var c = 0 - compileSeparately(List(codeA, codeB), extraArgs = InlineWarningTest.args, afterEach = removeImpl, allowMessage = i => {c += 1; i.msg contains warn}) - assert(c == 1, c) - - // only summary here - compileSeparately(List(codeA, codeB), extraArgs = InlineWarningTest.argsNoWarn, afterEach = removeImpl, allowMessage = _.msg contains "there was one inliner warning") - } - - @Test def handlerNonEmptyStack(): Unit = { val code = """class C { @@ -125,12 +103,12 @@ class InlineWarningTest extends ClearAfterClass { val warns = List( """failed to determine if bar should be inlined: |The method bar()I could not be found in the class A or any of its parents. - |Note that the following parent classes are defined in Java sources (mixed compilation), no bytecode is available: A""".stripMargin, + |Note that the parent class A is defined in a Java source (mixed compilation), no bytecode is available.""".stripMargin, """B::flop()I is annotated @inline but could not be inlined: |Failed to check if B::flop()I can be safely inlined to B without causing an IllegalAccessError. Checking instruction INVOKESTATIC A.bar ()I failed: |The method bar()I could not be found in the class A or any of its parents. - |Note that the following parent classes are defined in Java sources (mixed compilation), no bytecode is available: A""".stripMargin) + |Note that the parent class A is defined in a Java source (mixed compilation), no bytecode is available.""".stripMargin) var c = 0 val List(b) = compile(scalaCode, List((javaCode, "A.java")), allowMessage = i => {c += 1; warns.tail.exists(i.msg contains _)}) @@ -172,6 +150,33 @@ class InlineWarningTest extends ClearAfterClass { } @Test + def dontWarnWhenNotIlnineAnnotated(): Unit = { + val code = + """class M { + | final def f(t: Int => Int) = { + | @noinline def nested = 0 + | nested + t(1) + | } + | def t = f(x => x + 1) + |} + | + |class N { + | def t(a: M) = a.f(x => x + 1) + |} + """.stripMargin + compile(code, allowMessage = _ => false) // no warnings allowed + + val warn = + """M::f(Lscala/Function1;)I could not be inlined: + |The callee M::f(Lscala/Function1;)I contains the instruction INVOKESPECIAL M.nested$1 ()I + |that would cause an IllegalAccessError when inlined into class N""".stripMargin + + var c = 0 + compile(code, compiler = compilerWarnAll, allowMessage = i => { c += 1; i.msg contains warn }) + assert(c == 1, c) + } + + @Test def cannotMixStrictfp(): Unit = { val code = """import annotation.strictfp diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala index 7ed0e13226..6460158e71 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala @@ -16,11 +16,11 @@ import scala.tools.partest.ASMConverters import ASMConverters._ import AsmUtils._ -import scala.collection.convert.decorateAsScala._ +import scala.collection.JavaConverters._ import scala.tools.testing.ClearAfterClass object InlinerIllegalAccessTest extends ClearAfterClass.Clearable { - var compiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:l:none") + var compiler = newCompiler(extraArgs = "-Yopt:l:none") def clear(): Unit = { compiler = null } } @@ -67,7 +67,7 @@ class InlinerIllegalAccessTest extends ClearAfterClass { check(dClass, assertEmpty) check(eClass, assertEmpty) // C is public, so accessible in E - byteCodeRepository.classes.clear() + byteCodeRepository.parsedClasses.clear() classBTypeFromInternalName.clear() cClass.access &= ~ACC_PUBLIC // ftw diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerSeparateCompilationTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerSeparateCompilationTest.scala index 5c9bd1c188..075513a2b7 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerSeparateCompilationTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerSeparateCompilationTest.scala @@ -13,16 +13,15 @@ import scala.tools.partest.ASMConverters import ASMConverters._ import AsmUtils._ -import scala.collection.convert.decorateAsScala._ +import scala.collection.JavaConverters._ object InlinerSeparateCompilationTest { - val args = "-Ybackend:GenBCode -Yopt:l:classpath" + val args = "-Yopt:l:classpath" } @RunWith(classOf[JUnit4]) class InlinerSeparateCompilationTest { import InlinerSeparateCompilationTest._ - import InlinerTest.{listStringLines, assertInvoke, assertNoInvoke} @Test def inlnieMixedinMember(): Unit = { @@ -44,7 +43,7 @@ class InlinerSeparateCompilationTest { """.stripMargin val warn = "T::f()I is annotated @inline but cannot be inlined: the method is not final and may be overridden" - val List(c, o, oMod, t, tCls) = compileClassesSeparately(List(codeA, codeB), args + " -Yopt-warnings", _.msg contains warn) + val List(c, o, oMod, t) = compileClassesSeparately(List(codeA, codeB), args + " -Yopt-warnings", _.msg contains warn) assertInvoke(getSingleMethod(c, "t1"), "T", "f") assertNoInvoke(getSingleMethod(c, "t2")) assertNoInvoke(getSingleMethod(c, "t3")) @@ -64,7 +63,7 @@ class InlinerSeparateCompilationTest { |} """.stripMargin - val List(c, t, tCls) = compileClassesSeparately(List(codeA, codeB), args) + val List(c, t) = compileClassesSeparately(List(codeA, codeB), args) assertNoInvoke(getSingleMethod(c, "t1")) } @@ -87,7 +86,7 @@ class InlinerSeparateCompilationTest { |} """.stripMargin - val List(c, t, tCls, u, uCls) = compileClassesSeparately(List(codeA, codeB), args) + val List(c, t, u) = compileClassesSeparately(List(codeA, codeB), args) for (m <- List("t1", "t2", "t3")) assertNoInvoke(getSingleMethod(c, m)) } @@ -108,8 +107,8 @@ class InlinerSeparateCompilationTest { |$assembly """.stripMargin - val List(a, aCls, t, tCls) = compileClassesSeparately(List(codeA, assembly), args) - assertNoInvoke(getSingleMethod(tCls, "f")) - assertNoInvoke(getSingleMethod(aCls, "n")) + val List(a, t) = compileClassesSeparately(List(codeA, assembly), args) + assertNoInvoke(getSingleMethod(t, "f")) + assertNoInvoke(getSingleMethod(a, "n")) } } 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 0309bb97cc..e2a495fb2b 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala @@ -6,17 +6,11 @@ import org.junit.runner.RunWith import org.junit.runners.JUnit4 import org.junit.Test import scala.collection.generic.Clearable -import scala.collection.mutable.ListBuffer -import scala.reflect.internal.util.BatchSourceFile import scala.tools.asm.Opcodes._ import org.junit.Assert._ import scala.tools.asm.tree._ -import scala.tools.asm.tree.analysis._ -import scala.tools.nsc.backend.jvm.opt.BytecodeUtils.AsmAnalyzer -import scala.tools.nsc.io._ import scala.tools.nsc.reporters.StoreReporter -import scala.tools.testing.AssertUtil._ import CodeGenTools._ import scala.tools.partest.ASMConverters @@ -25,49 +19,43 @@ import AsmUtils._ import BackendReporting._ -import scala.collection.convert.decorateAsScala._ +import scala.collection.JavaConverters._ import scala.tools.testing.ClearAfterClass object InlinerTest extends ClearAfterClass.Clearable { - val args = "-Ybackend:GenBCode -Yopt:l:classpath -Yopt-warnings" + val args = "-Yopt:l:classpath -Yopt-warnings" var compiler = newCompiler(extraArgs = args) + var inlineOnlyCompiler = newCompiler(extraArgs = "-Yopt:inline-project") // allows inspecting the caches after a compilation run - def notPerRun: List[Clearable] = List(compiler.genBCode.bTypes.classBTypeFromInternalName, compiler.genBCode.bTypes.byteCodeRepository.classes, compiler.genBCode.bTypes.callGraph.callsites) + def notPerRun: List[Clearable] = List( + compiler.genBCode.bTypes.classBTypeFromInternalName, + compiler.genBCode.bTypes.byteCodeRepository.compilingClasses, + compiler.genBCode.bTypes.byteCodeRepository.parsedClasses, + compiler.genBCode.bTypes.callGraph.callsites) notPerRun foreach compiler.perRunCaches.unrecordCache - def clear(): Unit = { compiler = null } - - implicit class listStringLines[T](val l: List[T]) extends AnyVal { - def stringLines = l.mkString("\n") - } - - def assertNoInvoke(m: Method): Unit = assertNoInvoke(m.instructions) - def assertNoInvoke(ins: List[Instruction]): Unit = { - assert(!ins.exists(_.isInstanceOf[Invoke]), ins.stringLines) - } - - def assertInvoke(m: Method, receiver: String, method: String): Unit = assertInvoke(m.instructions, receiver, method) - def assertInvoke(l: List[Instruction], receiver: String, method: String): Unit = { - assert(l.exists { - case Invoke(_, `receiver`, `method`, _, _) => true - case _ => false - }, l.stringLines) - } + def clear(): Unit = { compiler = null; inlineOnlyCompiler = null } } @RunWith(classOf[JUnit4]) class InlinerTest extends ClearAfterClass { ClearAfterClass.stateToClear = InlinerTest - import InlinerTest.{listStringLines, assertInvoke, assertNoInvoke} - val compiler = InlinerTest.compiler import compiler.genBCode.bTypes._ + import compiler.genBCode.bTypes.backendUtils._ + import inlinerHeuristics._ + + val inlineOnlyCompiler = InlinerTest.inlineOnlyCompiler def compile(scalaCode: String, javaCode: List[(String, String)] = Nil, allowMessage: StoreReporter#Info => Boolean = _ => false): List[ClassNode] = { InlinerTest.notPerRun.foreach(_.clear()) compileClasses(compiler)(scalaCode, javaCode, allowMessage) + // Use the class nodes stored in the byteCodeRepository. The ones returned by compileClasses are not the same, + // these are created new from the classfile byte array. They are completely separate instances which cannot + // be used to look up methods / callsites in the callGraph hash maps for example. + byteCodeRepository.compilingClasses.valuesIterator.toList.sortBy(_.name) } def checkCallsite(callsite: callGraph.Callsite, callee: MethodNode) = { @@ -79,27 +67,25 @@ class InlinerTest extends ClearAfterClass { assert(callsite.callee.get.callee == callee, callsite.callee.get.callee.name) } - // inline first invocation of f into g in class C - def inlineTest(code: String, mod: ClassNode => Unit = _ => ()): (MethodNode, Option[CannotInlineWarning]) = { - val List(cls) = compile(code) - mod(cls) - val clsBType = classBTypeFromParsedClassfile(cls.name) + def getCallsite(method: MethodNode, calleeName: String) = callGraph.callsites(method).valuesIterator.find(_.callee.get.callee.name == calleeName).get - val List(f, g) = cls.methods.asScala.filter(m => Set("f", "g")(m.name)).toList.sortBy(_.name) - val fCall = g.instructions.iterator.asScala.collect({ case i: MethodInsnNode if i.name == "f" => i }).next() + def gMethAndFCallsite(code: String, mod: ClassNode => Unit = _ => ()) = { + val List(c) = compile(code) + mod(c) + val gMethod = findAsmMethod(c, "g") + val fCall = getCallsite(gMethod, "f") + (gMethod, fCall) + } - val analyzer = new AsmAnalyzer(g, clsBType.internalName) + def canInlineTest(code: String, mod: ClassNode => Unit = _ => ()): Option[OptimizerWarning] = { + val cs = gMethAndFCallsite(code, mod)._2 + inliner.earlyCanInlineCheck(cs) orElse inliner.canInlineBody(cs) + } - val r = inliner.inline( - fCall, - analyzer.frameAt(fCall).getStackSize, - g, - clsBType, - f, - clsBType, - receiverKnownNotNull = true, - keepLineNumbers = true) - (g, r) + def inlineTest(code: String, mod: ClassNode => Unit = _ => ()): MethodNode = { + val (gMethod, fCall) = gMethAndFCallsite(code, mod) + inliner.inline(InlineRequest(fCall, Nil)) + gMethod } @Test @@ -111,10 +97,10 @@ class InlinerTest extends ClearAfterClass { |} """.stripMargin - val (g, _) = inlineTest(code) + val g = inlineTest(code) val gConv = convertMethod(g) - assertSameCode(gConv.instructions.dropNonOp, + assertSameCode(gConv, List( VarOp(ALOAD, 0), VarOp(ASTORE, 1), // store this Op(ICONST_1), VarOp(ISTORE, 2), Jump(GOTO, Label(10)), // store return value @@ -145,16 +131,23 @@ class InlinerTest extends ClearAfterClass { // See also discussion around ATHROW in BCodeBodyBuilder - val (g, _) = inlineTest(code) - val expectedInlined = List( - VarOp(ALOAD, 0), VarOp(ASTORE, 1), // store this - Field(GETSTATIC, "scala/Predef$", "MODULE$", "Lscala/Predef$;"), Invoke(INVOKEVIRTUAL, "scala/Predef$", "$qmark$qmark$qmark", "()Lscala/runtime/Nothing$;", false)) // inlined call to ??? + val g = inlineTest(code) - assertSameCode(convertMethod(g).instructions.dropNonOp.take(4), expectedInlined) + val invokeQQQ = List( + Field(GETSTATIC, "scala/Predef$", "MODULE$", "Lscala/Predef$;"), + Invoke(INVOKEVIRTUAL, "scala/Predef$", "$qmark$qmark$qmark", "()Lscala/runtime/Nothing$;", false)) + + val gBeforeLocalOpt = VarOp(ALOAD, 0) :: VarOp(ASTORE, 1) :: invokeQQQ ::: List( + VarOp(ASTORE, 2), + Jump(GOTO, Label(11)), + Label(11), + VarOp(ALOAD, 2), + Op(ATHROW)) + + assertSameCode(convertMethod(g), gBeforeLocalOpt) compiler.genBCode.bTypes.localOpt.methodOptimizations(g, "C") - assertSameCode(convertMethod(g).instructions.dropNonOp, - expectedInlined ++ List(VarOp(ASTORE, 2), VarOp(ALOAD, 2), Op(ATHROW))) + assertSameCode(convertMethod(g), invokeQQQ :+ Op(ATHROW)) } @Test @@ -166,11 +159,11 @@ class InlinerTest extends ClearAfterClass { |} """.stripMargin - val (_, can) = inlineTest(code, cls => { + val can = canInlineTest(code, cls => { val f = cls.methods.asScala.find(_.name == "f").get f.access |= ACC_SYNCHRONIZED }) - assert(can.get.isInstanceOf[SynchronizedMethod], can) + assert(can.nonEmpty && can.get.isInstanceOf[SynchronizedMethod], can) } @Test @@ -181,7 +174,7 @@ class InlinerTest extends ClearAfterClass { | def g = f + 1 |} """.stripMargin - val (_, r) = inlineTest(code) + val r = canInlineTest(code) assert(r.isEmpty, r) } @@ -195,8 +188,8 @@ class InlinerTest extends ClearAfterClass { | def g = println(f) |} """.stripMargin - val (_, r) = inlineTest(code) - assert(r.get.isInstanceOf[MethodWithHandlerCalledOnNonEmptyStack], r) + val r = canInlineTest(code) + assert(r.nonEmpty && r.get.isInstanceOf[MethodWithHandlerCalledOnNonEmptyStack], r) } @Test @@ -216,29 +209,10 @@ class InlinerTest extends ClearAfterClass { """.stripMargin val List(c, d) = compile(code) - - val cTp = classBTypeFromParsedClassfile(c.name) - val dTp = classBTypeFromParsedClassfile(d.name) - - val g = c.methods.asScala.find(_.name == "g").get - val h = d.methods.asScala.find(_.name == "h").get - val gCall = h.instructions.iterator.asScala.collect({ - case m: MethodInsnNode if m.name == "g" => m - }).next() - - val analyzer = new AsmAnalyzer(h, dTp.internalName) - - val r = inliner.inline( - gCall, - analyzer.frameAt(gCall).getStackSize, - h, - dTp, - g, - cTp, - receiverKnownNotNull = true, - keepLineNumbers = true) - - assert(r.get.isInstanceOf[IllegalAccessInstruction], r) + val hMeth = findAsmMethod(d, "h") + val gCall = getCallsite(hMeth, "g") + val r = inliner.canInlineBody(gCall) + assert(r.nonEmpty && r.get.isInstanceOf[IllegalAccessInstruction], r) } @Test @@ -273,7 +247,7 @@ class InlinerTest extends ClearAfterClass { assert(gIns contains invokeG, gIns) // f is inlined into g, g invokes itself recursively assert(callGraph.callsites.size == 3, callGraph.callsites) - for (callsite <- callGraph.callsites.values if methods.contains(callsite.callsiteMethod)) { + for (callsite <- callGraph.callsites.valuesIterator.flatMap(_.valuesIterator) if methods.contains(callsite.callsiteMethod)) { checkCallsite(callsite, g) } } @@ -295,8 +269,8 @@ class InlinerTest extends ClearAfterClass { assert(gIns.count(_ == invokeG) == 2, gIns) assert(hIns.count(_ == invokeG) == 2, hIns) - assert(callGraph.callsites.size == 7, callGraph.callsites) - for (callsite <- callGraph.callsites.values if methods.contains(callsite.callsiteMethod)) { + assert(callGraph.callsites.valuesIterator.flatMap(_.valuesIterator).size == 7, callGraph.callsites) + for (callsite <- callGraph.callsites.valuesIterator.flatMap(_.valuesIterator) if methods.contains(callsite.callsiteMethod)) { checkCallsite(callsite, g) } } @@ -336,7 +310,7 @@ class InlinerTest extends ClearAfterClass { |} """.stripMargin val List(c) = compile(code) - assert(callGraph.callsites.values exists (_.callsiteInstruction.name == "clone")) + assert(callGraph.callsites.valuesIterator.flatMap(_.valuesIterator) exists (_.callsiteInstruction.name == "clone")) } @Test @@ -349,7 +323,7 @@ class InlinerTest extends ClearAfterClass { | def g(t: T) = t.f |} """.stripMargin - val List(c, t, tClass) = compile(code) + val List(c, t) = compile(code) assertNoInvoke(getSingleMethod(c, "g")) } @@ -375,26 +349,14 @@ class InlinerTest extends ClearAfterClass { """.stripMargin val List(c) = compile(code) - val f = c.methods.asScala.find(_.name == "f").get - val callsiteIns = f.instructions.iterator().asScala.collect({ case c: MethodInsnNode => c }).next() - val clsBType = classBTypeFromParsedClassfile(c.name) - val analyzer = new AsmAnalyzer(f, clsBType.internalName) - - val integerClassBType = classBTypeFromInternalName("java/lang/Integer") - val lowestOneBitMethod = byteCodeRepository.methodNode(integerClassBType.internalName, "lowestOneBit", "(I)I").get._1 - - val r = inliner.inline( - callsiteIns, - analyzer.frameAt(callsiteIns).getStackSize, - f, - clsBType, - lowestOneBitMethod, - integerClassBType, - receiverKnownNotNull = false, - keepLineNumbers = false) + val fMeth = findAsmMethod(c, "f") + val call = getCallsite(fMeth, "lowestOneBit") - assert(r.isEmpty, r) - val ins = instructionsFromMethod(f) + val warning = inliner.canInlineBody(call) + assert(warning.isEmpty, warning) + + inliner.inline(InlineRequest(call, Nil)) + val ins = instructionsFromMethod(fMeth) // no invocations, lowestOneBit is inlined assertNoInvoke(ins) @@ -425,7 +387,8 @@ class InlinerTest extends ClearAfterClass { |} """.stripMargin - val List(c) = compile(code) + // use a compiler without local optimizations (cleanups) + val List(c) = compileClasses(inlineOnlyCompiler)(code) val ms @ List(f1, f2, g1, g2) = c.methods.asScala.filter(_.name.length == 2).toList // stack height at callsite of f1 is 1, so max of g1 after inlining is max of f1 + 1 @@ -465,7 +428,7 @@ class InlinerTest extends ClearAfterClass { """B::flop()I is annotated @inline but could not be inlined: |Failed to check if B::flop()I can be safely inlined to B without causing an IllegalAccessError. Checking instruction INVOKESTATIC A.bar ()I failed: |The method bar()I could not be found in the class A or any of its parents. - |Note that the following parent classes are defined in Java sources (mixed compilation), no bytecode is available: A""".stripMargin + |Note that the parent class A is defined in a Java source (mixed compilation), no bytecode is available.""".stripMargin var c = 0 val List(b) = compile(scalaCode, List((javaCode, "A.java")), allowMessage = i => {c += 1; i.msg contains warn}) @@ -488,26 +451,13 @@ class InlinerTest extends ClearAfterClass { | def t2(c: C) = c.f |} """.stripMargin - val List(c, t, tClass) = compile(code) + val List(c, t) = compile(code) // both are just `return 1`, no more calls assertNoInvoke(getSingleMethod(c, "t1")) assertNoInvoke(getSingleMethod(c, "t2")) } @Test - def inlineMixinMethods(): Unit = { - val code = - """trait T { - | @inline final def f = 1 - |} - |class C extends T - """.stripMargin - val List(c, t, tClass) = 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 { @@ -521,7 +471,7 @@ class InlinerTest extends ClearAfterClass { | def t2 = g |} """.stripMargin - val List(c, t, tClass, u, uClass) = compile(code) + val List(c, t, u) = compile(code) assertNoInvoke(getSingleMethod(c, "t1")) assertNoInvoke(getSingleMethod(c, "t2")) } @@ -541,7 +491,7 @@ class InlinerTest extends ClearAfterClass { "C::f()I is annotated @inline but cannot be inlined: the method is not final and may be overridden", "T::f()I is annotated @inline but cannot be inlined: the method is not final and may be overridden") var count = 0 - val List(c, t, tClass) = compile(code, allowMessage = i => {count += 1; warns.exists(i.msg contains _)}) + val List(c, t) = compile(code, allowMessage = i => {count += 1; warns.exists(i.msg contains _)}) assert(count == 2, count) assertInvoke(getSingleMethod(c, "t1"), "T", "f") assertInvoke(getSingleMethod(c, "t2"), "C", "f") @@ -557,7 +507,7 @@ class InlinerTest extends ClearAfterClass { | def t1(t: T) = t.f |} """.stripMargin - val List(c, t, tClass) = compile(code) + val List(c, t) = compile(code) assertNoInvoke(getSingleMethod(c, "t1")) } @@ -569,7 +519,7 @@ class InlinerTest extends ClearAfterClass { |} |object O extends T { | @inline def g = 1 - | // mixin generates `def f = T$class.f(this)`, which is inlined here (we get ICONST_0) + | // mixin generates `def f = super[T].f`, which is inlined here (we get ICONST_0) |} |class C { | def t1 = O.f // the mixin method of O is inlined, so we directly get the ICONST_0 @@ -579,10 +529,10 @@ class InlinerTest extends ClearAfterClass { """.stripMargin val warn = "T::f()I is annotated @inline but cannot be inlined: the method is not final and may be overridden" var count = 0 - val List(c, oMirror, oModule, t, tClass) = compile(code, allowMessage = i => {count += 1; i.msg contains warn}) + 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")) @@ -598,21 +548,19 @@ class InlinerTest extends ClearAfterClass { |} |trait Assembly extends T { | @inline final def g = 1 - | @inline final def n = m // inlined. (*) - | // (*) the declaration class of m is T. the signature of T$class.m is m(LAssembly;)I. so we need the self type to build the - | // signature. then we can look up the MethodNode of T$class.m and then rewrite the INVOKEINTERFACE to INVOKESTATIC. + | @inline final def n = m // inlined (m is final) |} |class C { - | def t1(a: Assembly) = a.f // like above, decl class is T, need self-type of T to rewrite the interface call to static. + | def t1(a: Assembly) = a.f // inlined (f is final) | def t2(a: Assembly) = a.n |} """.stripMargin - val List(assembly, assemblyClass, c, t, tClass) = compile(code) + val List(assembly, c, t) = compile(code) - assertNoInvoke(getSingleMethod(tClass, "f")) + assertNoInvoke(getSingleMethod(t, "f")) - assertNoInvoke(getSingleMethod(assemblyClass, "n")) + assertNoInvoke(getSingleMethod(assembly, "n")) assertNoInvoke(getSingleMethod(c, "t1")) assertNoInvoke(getSingleMethod(c, "t2")) @@ -647,30 +595,30 @@ class InlinerTest extends ClearAfterClass { val code = """trait T1 { | @inline def f: Int = 0 - | @inline def g1 = f // not inlined: f not final, so T1$class.g1 has an interface call T1.f + | @inline def g1 = f // not inlined: f not final |} | - |// erased self-type (used in impl class for `self` parameter): T1 + |// erased self-type: T1 |trait T2a { self: T1 with T2a => | @inline override final def f = 1 - | @inline def g2a = f // inlined: resolved as T2a.f, which is re-written to T2a$class.f, so T2a$class.g2a has ICONST_1 + | @inline def g2a = f // inlined: resolved as T2a.f |} | |final class Ca extends T1 with T2a { - | // mixin generates accessors like `def g1 = T1$class.g1`, the impl class method call is inlined into the accessor. + | // mixin generates accessors like `def g1 = super[T1].g1`, the impl super call is inlined into the accessor. | | def m1a = g1 // call to accessor, inlined, we get the interface call T1.f | def m2a = g2a // call to accessor, inlined, we get ICONST_1 | def m3a = f // call to accessor, inlined, we get ICONST_1 | - | def m4a(t: T1) = t.f // T1.f is not final, so not inlined, interface call to T1.f - | def m5a(t: T2a) = t.f // re-written to T2a$class.f, inlined, ICONST_1 + | def m4a(t: T1) = t.f // T1.f is not final, so not inlined, we get an interface call T1.f + | def m5a(t: T2a) = t.f // inlined, we get ICONST_1 |} | |// erased self-type: T2b |trait T2b { self: T2b with T1 => | @inline override final def f = 1 - | @inline def g2b = f // not inlined: resolved as T1.f, so T2b$class.g2b has an interface call T1.f + | @inline def g2b = f // not inlined: resolved as T1.f, we get an interface call T1.f |} | |final class Cb extends T1 with T2b { @@ -679,23 +627,17 @@ class InlinerTest extends ClearAfterClass { | def m3b = f // inlined, we get ICONST_1 | | def m4b(t: T1) = t.f // T1.f is not final, so not inlined, interface call to T1.f - | def m5b(t: T2b) = t.f // re-written to T2b$class.f, inlined, ICONST_1 + | def m5b(t: T2b) = t.f // inlined, ICONST_1 |} """.stripMargin val warning = "T1::f()I is annotated @inline but cannot be inlined: the method is not final and may be overridden" var count = 0 - val List(ca, cb, t1, t1C, t2a, t2aC, t2b, t2bC) = compile(code, allowMessage = i => {count += 1; i.msg contains warning}) + val List(ca, cb, t1, t2a, t2b) = compile(code, allowMessage = i => {count += 1; i.msg contains warning}) assert(count == 4, count) // see comments, f is not inlined 4 times - val t2aCfDesc = t2aC.methods.asScala.find(_.name == "f").get.desc - assert(t2aCfDesc == "(LT1;)I", t2aCfDesc) // self-type of T2a is T1 - - val t2bCfDesc = t2bC.methods.asScala.find(_.name == "f").get.desc - assert(t2bCfDesc == "(LT2b;)I", t2bCfDesc) // self-type of T2b is T2b - - assertNoInvoke(getSingleMethod(t2aC, "g2a")) - assertInvoke(getSingleMethod(t2bC, "g2b"), "T1", "f") + assertNoInvoke(getSingleMethod(t2a, "g2a")) + assertInvoke(getSingleMethod(t2b, "g2b"), "T1", "f") assertInvoke(getSingleMethod(ca, "m1a"), "T1", "f") assertNoInvoke(getSingleMethod(ca, "m2a")) // no invoke, see comment on def g2a @@ -732,14 +674,13 @@ class InlinerTest extends ClearAfterClass { val code = """class C { | trait T { @inline final def f = 1 } - | class D extends T{ + | class D extends T { | def m(t: T) = t.f | } - | | def m(d: D) = d.f |} """.stripMargin - val List(c, d, t, tC) = compile(code) + val List(c, d, t) = compile(code) assertNoInvoke(getSingleMethod(d, "m")) assertNoInvoke(getSingleMethod(c, "m")) } @@ -754,9 +695,9 @@ class InlinerTest extends ClearAfterClass { | def t2(t: T) = t.f(2) |} """.stripMargin - val List(c, t, tc) = compile(code) - val t1 = getSingleMethod(tc, "t1") - val t2 = getSingleMethod(tc, "t2") + val List(c, t) = compile(code) + val t1 = getSingleMethod(t, "t1") + val t2 = getSingleMethod(t, "t2") val cast = TypeOp(CHECKCAST, "C") Set(t1, t2).foreach(m => assert(m.instructions.contains(cast), m.instructions)) } @@ -803,8 +744,8 @@ class InlinerTest extends ClearAfterClass { | final val d = 3 | final val d1: Int = 3 | - | @noinline def f = 5 // re-written to T$class - | @noinline final def g = 6 // re-written + | @noinline def f = 5 + | @noinline final def g = 6 | | @noinline def h: Int | @inline def i: Int @@ -817,8 +758,8 @@ class InlinerTest extends ClearAfterClass { | final val d = 3 | final val d1: Int = 3 | - | @noinline def f = 5 // not re-written (not final) - | @noinline final def g = 6 // re-written + | @noinline def f = 5 + | @noinline final def g = 6 | | @noinline def h: Int | @inline def i: Int @@ -835,7 +776,7 @@ class InlinerTest extends ClearAfterClass { |} """.stripMargin - val List(c, t, tClass, u, uClass) = compile(code, allowMessage = _.msg contains "i()I is annotated @inline but cannot be inlined") + val List(c, t, u) = compile(code, allowMessage = _.msg contains "i()I is annotated @inline but cannot be inlined") val m1 = getSingleMethod(c, "m1") assertInvoke(m1, "T", "a") assertInvoke(m1, "T", "b") @@ -844,8 +785,8 @@ class InlinerTest extends ClearAfterClass { assertNoInvoke(getSingleMethod(c, "m2")) val m3 = getSingleMethod(c, "m3") - assertInvoke(m3, "T$class", "f") - assertInvoke(m3, "T$class", "g") + assertInvoke(m3, "T", "f") + assertInvoke(m3, "T", "g") assertInvoke(m3, "T", "h") assertInvoke(m3, "T", "i") @@ -858,7 +799,7 @@ class InlinerTest extends ClearAfterClass { val m6 = getSingleMethod(c, "m6") assertInvoke(m6, "U", "f") - assertInvoke(m6, "U$class", "g") + assertInvoke(m6, "U", "g") assertInvoke(m6, "U", "h") assertInvoke(m6, "U", "i") } @@ -892,7 +833,7 @@ class InlinerTest extends ClearAfterClass { val warn = """failed to determine if <init> should be inlined: |The method <init>()V could not be found in the class A$Inner or any of its parents. - |Note that the following parent classes could not be found on the classpath: A$Inner""".stripMargin + |Note that the parent class A$Inner could not be found on the classpath.""".stripMargin var c = 0 @@ -988,7 +929,588 @@ class InlinerTest extends ClearAfterClass { val List(c) = compile(code) val t = getSingleMethod(c, "t").instructions assertNoInvoke(t) - assert(2 == t.collect({case Ldc(_, "hai!") => }).size) // twice the body of f + assert(1 == t.collect({case Ldc(_, "hai!") => }).size) // push-pop eliminates the first LDC("hai!") assert(1 == t.collect({case Jump(IFNONNULL, _) => }).size) // one single null check } + + @Test + def inlineIndyLambda(): Unit = { + val code = + """object M { + | @inline def m(s: String) = { + | val f = (x: String) => x.trim + | f(s) + | } + |} + |class C { + | @inline final def m(s: String) = { + | val f = (x: String) => x.trim + | f(s) + | } + | def t1 = m("foo") + | def t2 = M.m("bar") + |} + """.stripMargin + + val List(c, _, _) = compile(code) + + val t1 = getSingleMethod(c, "t1") + assertNoIndy(t1) + // the indy call is inlined into t, and the closure elimination rewrites the closure invocation to the body method + assertInvoke(t1, "C", "C$$$anonfun$2") + + val t2 = getSingleMethod(c, "t2") + assertNoIndy(t2) + assertInvoke(t2, "M$", "M$$$anonfun$1") + } + + @Test + def inlinePostRequests(): Unit = { + val code = + """class C { + | final def f = 10 + | final def g = f + 19 + | final def h = g + 29 + | final def i = h + 39 + |} + """.stripMargin + + val List(c) = compile(code) + val hMeth = findAsmMethod(c, "h") + val gMeth = findAsmMethod(c, "g") + val iMeth = findAsmMethod(c, "i") + val fCall = getCallsite(gMeth, "f") + val gCall = getCallsite(hMeth, "g") + val hCall = getCallsite(iMeth, "h") + + val warning = inliner.canInlineBody(gCall) + assert(warning.isEmpty, warning) + + inliner.inline(InlineRequest(hCall, + post = List(InlineRequest(gCall, + post = List(InlineRequest(fCall, Nil)))))) + assertNoInvoke(convertMethod(iMeth)) // no invoke in i: first h is inlined, then the inlined call to g is also inlined, etc for f + assertInvoke(convertMethod(gMeth), "C", "f") // g itself still has the call to f + } + + @Test + def postRequestSkipAlreadyInlined(): Unit = { + val code = + """class C { + | final def a = 10 + | final def b = a + 20 + | final def c = b + 30 + | final def d = c + 40 + |} + """.stripMargin + + val List(cl) = compile(code) + val List(b, c, d) = List("b", "c", "d").map(findAsmMethod(cl, _)) + val aCall = getCallsite(b, "a") + val bCall = getCallsite(c, "b") + val cCall = getCallsite(d, "c") + + inliner.inline(InlineRequest(bCall, Nil)) + + val req = InlineRequest(cCall, + List(InlineRequest(bCall, + List(InlineRequest(aCall, Nil))))) + inliner.inline(req) + + assertNoInvoke(convertMethod(d)) + } + + @Test + def inlineAnnotatedCallsite(): Unit = { + val code = + """class C { + | final def a(x: Int, f: Int => Int): Int = f(x) + | final def b(x: Int) = x + | final def c = 1 + | final def d[T] = 2 + | final def e[T](x: T) = c + | final def f[T](x: T) = println(x) + | final def g(x: Int)(y: Int) = x + | + | def t1 = a(10, _ + 1) + | def t2 = a(10, _ + 1): @noinline + | def t3 = b(3) + | def t4 = b(3): @inline + | def t5 = c: @inline + | def t6 = d[Int]: @inline + | def t7 = e[Int](2): @inline + | def t8 = f[Int](2): @inline + | def t9 = g(1)(2): @inline + |} + """.stripMargin + + val List(c) = compile(code) + assertInvoke(getSingleMethod(c, "t1"), "C", "C$$$anonfun$1") + assertInvoke(getSingleMethod(c, "t2"), "C", "a") + assertInvoke(getSingleMethod(c, "t3"), "C", "b") + assertNoInvoke(getSingleMethod(c, "t4")) + assertNoInvoke(getSingleMethod(c, "t5")) + assertNoInvoke(getSingleMethod(c, "t6")) + assertInvoke(getSingleMethod(c, "t7"), "C", "c") + assertInvoke(getSingleMethod(c, "t8"), "scala/Predef$", "println") + assertNoInvoke(getSingleMethod(c, "t9")) + } + + @Test + def inlineNoInlineOverride(): Unit = { + val code = + """class C { + | @inline final def f1(x: Int) = x + | @noinline final def f2(x: Int) = x + | final def f3(x: Int) = x + | + | def t1 = f1(1) // inlined + | def t2 = f2(1) // not inlined + | def t3 = f1(1): @noinline // not inlined + | def t4 = f2(1): @inline // not inlined (cannot override the def-site @noinline) + | def t5 = f3(1): @inline // inlined + | def t6 = f3(1): @noinline // not inlined + | + | def t7 = f1(1) + (f3(1): @inline) // without parenthesis, the ascription encloses the entire expression.. + | def t8 = f1(1) + (f1(1): @noinline) + | def t9 = f1(1) + f1(1) : @noinline // the ascription goes on the entire expression, so on the + invocation.. both f1 are inlined + |} + """.stripMargin + + val List(c) = compile(code) + assertNoInvoke(getSingleMethod(c, "t1")) + assertInvoke(getSingleMethod(c, "t2"), "C", "f2") + assertInvoke(getSingleMethod(c, "t3"), "C", "f1") + assertInvoke(getSingleMethod(c, "t4"), "C", "f2") + assertNoInvoke(getSingleMethod(c, "t5")) + assertInvoke(getSingleMethod(c, "t6"), "C", "f3") + assertNoInvoke(getSingleMethod(c, "t7")) + assertInvoke(getSingleMethod(c, "t8"), "C", "f1") + assertNoInvoke(getSingleMethod(c, "t9")) + } + + @Test + def inlineHigherOrder(): Unit = { + val code = + """class C { + | final def h(f: Int => Int): Int = f(0) + | def t1 = h(x => x + 1) + | def t2 = { + | val fun = (x: Int) => x + 1 + | h(fun) + | } + | def t3(f: Int => Int) = h(f) + | def t4(f: Int => Int) = { + | val fun = f + | h(fun) + | } + | def t5 = h(Map(0 -> 10)) // not currently inlined + |} + """.stripMargin + + val List(c) = compile(code) + assertInvoke(getSingleMethod(c, "t1"), "C", "C$$$anonfun$1") + assertInvoke(getSingleMethod(c, "t2"), "C", "C$$$anonfun$2") + assertInvoke(getSingleMethod(c, "t3"), "scala/Function1", "apply$mcII$sp") + assertInvoke(getSingleMethod(c, "t4"), "scala/Function1", "apply$mcII$sp") + assertInvoke(getSingleMethod(c, "t5"), "C", "h") + } + + @Test + def twoStepNoInlineHandler(): Unit = { + val code = + """class C { + | @inline final def f = try 1 catch { case _: Throwable => 2 } + | @inline final def g = f + | def t = println(g) // cannot inline g onto non-empty stack once that f was inlined into g + |} + """.stripMargin + + val warn = + """C::g()I is annotated @inline but could not be inlined: + |The operand stack at the callsite in C::t()V contains more values than the + |arguments expected by the callee C::g()I. These values would be discarded + |when entering an exception handler declared in the inlined method.""".stripMargin + + val List(c) = compile(code, allowMessage = _.msg contains warn) + assertInvoke(getSingleMethod(c, "t"), "C", "g") + } + + @Test + def twoStepNoInlinePrivate(): Unit = { + val code = + """class C { + | @inline final def g = { + | @noinline def f = 0 + | f + | } + | @inline final def h = g // after inlining g, h has an invocate of private method f$1 + |} + |class D { + | def t(c: C) = c.h // cannot inline + |} + """.stripMargin + + val warn = + """C::h()I is annotated @inline but could not be inlined: + |The callee C::h()I contains the instruction INVOKESPECIAL C.f$1 ()I + |that would cause an IllegalAccessError when inlined into class D.""".stripMargin + + val List(c, d) = compile(code, allowMessage = _.msg contains warn) + assertInvoke(getSingleMethod(c, "h"), "C", "f$1") + assertInvoke(getSingleMethod(d, "t"), "C", "h") + } + + @Test + def twoStepInlinePrivate(): Unit = { + val code = + """class C { + | @inline final def g = { // initially, g invokes the private method f$1, but then f$1 is inlined + | @inline def f = 0 + | f + | } + |} + |class D { + | def t(c: C) = c.g // can inline + |} + """.stripMargin + + val List(c, d) = compile(code) + assertNoInvoke(getSingleMethod(c, "g")) + assertNoInvoke(getSingleMethod(d, "t")) + } + + @Test + def optimizeSpecializedClosures(): Unit = { + val code = + """class ValKl(val x: Int) extends AnyVal + | + |class C { + | def t1 = { + | // IndyLambda: SAM type is JFunction1$mcII$sp, SAM is apply$mcII$sp(I)I, body method is $anonfun(I)I + | val f = (x: Int) => x + 1 + | // invocation of apply$mcII$sp(I)I, matches the SAM in IndyLambda. no boxing / unboxing needed. + | f(10) + | // opt: re-write the invocation to the body method + | } + | + | @inline final def m1a(f: Long => Int) = f(1l) + | def t1a = m1a(l => l.toInt) // after inlining m1a, we have the same situation as in t1 + | + | def t2 = { + | // there is no specialized variant of Function2 for this combination of types, so the IndyLambda has to create a generic Function2. + | // IndyLambda: SAM type is JFunction2, SAM is apply(ObjectObject)Object, body method is $anonfun$adapted(ObjectObject)Object + | val f = (b: Byte, i: Int) => i + b + | // invocation of apply(ObjectOjbect)Object, matches SAM in IndyLambda. arguments are boxed, result unboxed. + | f(1, 2) + | // opt: re-wrtie to $anonfun$adapted + | // inline that call, then we get box-unbox pairs (can be eliminated) and a call to $anonfun(BI)I + | } + | + | def t3 = { + | // similar to t2: for functions with value class parameters, IndyLambda always uses the generic Function version. + | // IndyLambda: SAM type is JFunction1, SAM is apply(Object)Object, body method is $anonfun$adapted(Object)Object + | val f = (a: ValKl) => a + | // invocation of apply(Object)Object, ValKl instance is created, result extracted + | f(new ValKl(1)) + | // opt: re-write to $anonfun$adapted. + | // inline that call, then we get value class instantiation-extraction pairs and a call to $anonfun(I)I + | } + | + | def t4 = { + | // IndyLambda: SAM type is JFunction1$mcII$sp, SAM is apply$mcII$sp(I)I, body method is $anonfun(I)I + | val f: Int => Any = (x: Int) => 1 + | // invocation of apply(Object)Object, argument is boxed. method name and type doesn't match IndyLambda. + | f(10) + | // opt: rewriting to the body method requires inserting an unbox operation for the argument, and a box operation for the result + | // that produces a box-unbox pair and a call to $anonfun(I)I + | } + | + | + | @inline final def m4a[T, U, V](f: (T, U) => V, x: T, y: U) = f(x, y) // invocation to generic apply(ObjectObject)Object + | def t4a = m4a((x: Int, y: Double) => 1l + x + y.toLong, 1, 2d) // IndyLambda uses specilized JFunction2$mcJID$sp. after inlining m4a, similar to t4. + | + | def t5 = { + | // no specialization for the comibnation of primitives + | // IndyLambda: SAM type is JFunction2, SAM is generic apply, body method is $anonfun$adapted + | val f: (Int, Byte) => Any = (x: Int, b: Byte) => 1 + | // invocation of generic apply. + | f(10, 3) + | // opt: re-write to $anonfun$adapted, inline that method. generates box-unbox pairs and a call to $anonfun(IB)I + | } + | + | def t5a = m4a((x: Int, y: Byte) => 1, 12, 31.toByte) // similar to t5 after inlining m4a + | + | // m6$mIVc$sp invokes apply$mcVI$sp + | @inline final def m6[@specialized(Int) T, @specialized(Unit) U](f: T => U, x: T): Unit = f(x) + | // IndyLambda: JFunction1$mcVI$sp, SAM is apply$mcVI$sp, body method $anonfun(I)V + | // invokes m6$mIVc$sp (Lscala/Function1;I)V + | def t6 = m6((x: Int) => (), 10) + | // opt: after inlining m6, the closure method invocation (apply$mcVI$sp) matches the IndyLambda, the call can be rewritten, no boxing + | + | // m7 invokes apply + | @inline final def m7[@specialized(Boolean) T, @specialized(Int) U](f: T => U, x: T): Unit = f(x) + | // IndyLambda: JFunction1, SAM is apply(Object)Object, body method is $anonfun$adapted(Obj)Obj + | // `true` is boxed before passing to m7 + | def t7 = m7((x: Boolean) => (), true) + | // opt: after inlining m7, the apply call is re-written to $anonfun$adapted, which is then inlined. + | // we get a box-unbox pair and a call to $anonfun(Z)V + | + | + | // invokes the generic apply(ObjObj)Obj + | @inline final def m8[T, U, V](f: (T, U) => V, x: T, y: U) = f(x, y) + | // IndyLambda: JFunction2$mcJID$sp, SAM is apply$mcJID$sp, body method $anonfun(ID)J + | // boxes the int and double arguments and calls m8, unboxToLong the result + | def t8 = m8((x: Int, y: Double) => 1l + x + y.toLong, 1, 2d) + | // opt: after inlining m8, rewrite to the body method $anonfun(ID)J, which requires inserting unbox operations for the params, box for the result + | // the box-unbox pairs can then be optimized away + | + | // m9$mVc$sp invokes apply$mcVI$sp + | @inline final def m9[@specialized(Unit) U](f: Int => U): Unit = f(1) + | // IndyLambda: JFunction1, SAM is apply(Obj)Obj, body method $anonfun$adapted(Ojb)Obj + | // invocation of m9$mVc$sp + | def t9 = m9(println) + | // opt: after inlining m9, rewrite to $anonfun$adapted(Ojb)Obj, which requires inserting a box operation for the parameter. + | // then we inline $adapted, which has signature (Obj)V. the `BoxedUnit.UNIT` from the body of $anonfun$adapted is eliminated by push-pop + | + | def t9a = (1 to 10) foreach println // similar to t9 + | + | def intCons(i: Int): Unit = () + | // IndyLambda: JFunction1$mcVI$sp, SAM is apply$mcVI$sp, body method $anonfun(I)V + | def t10 = m9(intCons) + | // after inlining m9, rewrite the apply$mcVI$sp call to the body method, no adaptations required + | + | def t10a = (1 to 10) foreach intCons // similar to t10 + |} + """.stripMargin + val List(c, _, _) = compile(code) + + assertSameSummary(getSingleMethod(c, "t1"), List(BIPUSH, "C$$$anonfun$1", IRETURN)) + assertSameSummary(getSingleMethod(c, "t1a"), List(LCONST_1, "C$$$anonfun$2", IRETURN)) + assertSameSummary(getSingleMethod(c, "t2"), List(ICONST_1, ICONST_2, "C$$$anonfun$3",IRETURN)) + + // val a = new ValKl(n); new ValKl(anonfun(a.x)).x + // value class instantiation-extraction should be optimized by boxing elim + assertSameSummary(getSingleMethod(c, "t3"), List( + NEW, DUP, ICONST_1, "<init>", ASTORE, + NEW, DUP, ALOAD, "x", + "C$$$anonfun$4", + "<init>", + "x", IRETURN)) + + assertSameSummary(getSingleMethod(c, "t4"), List(BIPUSH, "C$$$anonfun$5", "boxToInteger", ARETURN)) + assertSameSummary(getSingleMethod(c, "t4a"), List(ICONST_1, LDC, "C$$$anonfun$6", LRETURN)) + assertSameSummary(getSingleMethod(c, "t5"), List(BIPUSH, ICONST_3, "C$$$anonfun$7", "boxToInteger", ARETURN)) + assertSameSummary(getSingleMethod(c, "t5a"), List(BIPUSH, BIPUSH, I2B, "C$$$anonfun$8", IRETURN)) + assertSameSummary(getSingleMethod(c, "t6"), List(BIPUSH, "C$$$anonfun$9", RETURN)) + assertSameSummary(getSingleMethod(c, "t7"), List(ICONST_1, "C$$$anonfun$10", RETURN)) + assertSameSummary(getSingleMethod(c, "t8"), List(ICONST_1, LDC, "C$$$anonfun$11", LRETURN)) + assertSameSummary(getSingleMethod(c, "t9"), List(ICONST_1, "boxToInteger", "C$$$anonfun$12", RETURN)) + + // t9a inlines Range.foreach, which is quite a bit of code, so just testing the core + assertInvoke(getSingleMethod(c, "t9a"), "C", "C$$$anonfun$13") + assertInvoke(getSingleMethod(c, "t9a"), "scala/runtime/BoxesRunTime", "boxToInteger") + + assertSameSummary(getSingleMethod(c, "t10"), List( + ICONST_1, ISTORE, + ALOAD, ILOAD, + "C$$$anonfun$14", RETURN)) + + // t10a inlines Range.foreach + assertInvoke(getSingleMethod(c, "t10a"), "C", "C$$$anonfun$15") + assertDoesNotInvoke(getSingleMethod(c, "t10a"), "boxToInteger") + } + + @Test + def refElimination(): Unit = { + val code = + """class C { + | def t1 = { + | var i = 0 + | @inline def inner() = i += 1 + | inner() + | i + | } + | + | final def m(f: Int => Unit) = f(10) + | def t2 = { + | var x = -1 // IntRef not yet eliminated: closure elimination does not + | m(i => if (i == 10) x = 1) // yet inline the anonfun method, need to improve the heuristsics + | x + | } + |} + """.stripMargin + val List(c) = compile(code) + assertSameCode(getSingleMethod(c, "t1"), List(Op(ICONST_0), Op(ICONST_1), Op(IADD), Op(IRETURN))) + assertEquals(getSingleMethod(c, "t2").instructions collect { case i: Invoke => i.owner +"."+ i.name }, List( + "scala/runtime/IntRef.create", "C.C$$$anonfun$1")) + } + + @Test + def tupleElimination(): Unit = { + val code = + """class C { + | @inline final def tpl[A, B](a: A, b: B) = (a, b) + | @inline final def t_1[A, B](t: (A, B)) = t._1 + | @inline final def t_2[A, B](t: (A, B)) = t._2 + | + | def t1 = { + | val t = (3, 4) // specialized tuple + | t_1(t) + t_2(t) // invocations to generic _1 / _2, box operation inserted when eliminated + | } + | + | def t2 = { + | val t = tpl(1, 2) // generic Tuple2[Integer, Integer] created + | t._1 + t._2 // invokes the specialized _1$mcI$sp, eliminating requires adding an unbox operation + | } + | + | @inline final def m = (1, 3) + | def t3 = { + | val (a, b) = m + | a - b + | } + | + | def t4 = { + | val ((a, b), (c, d)) = (m, m) + | a + b + c + d + | } + | + | def t5 = m match { + | case (1, y) => y + | case (x, y) => x * y + | } + |} + """.stripMargin + val List(c) = compile(code) + assertSameCode(getSingleMethod(c, "t1"), List(Op(ICONST_3), Op(ICONST_4), Op(IADD), Op(IRETURN))) + assertSameCode(getSingleMethod(c, "t2"), List(Op(ICONST_1), Op(ICONST_2), Op(IADD), Op(IRETURN))) + assertSameCode(getSingleMethod(c, "t3"), List(Op(ICONST_1), Op(ICONST_3), Op(ISUB), Op(IRETURN))) + assertNoInvoke(getSingleMethod(c, "t4")) + assertNoInvoke(getSingleMethod(c, "t5")) + } + + @Test + def redundantCasts(): Unit = { + + // we go through the hoop of inlining the casts because erasure eliminates `asInstanceOf` calls + // that are statically known to succeed. For example the following cast is removed by erasure: + // `(if (b) c else d).asInstanceOf[C]` + + val code = + """class C { + | @inline final def asO(a: Any) = a.asInstanceOf[Object] + | @inline final def asC(a: Any) = a.asInstanceOf[C] + | @inline final def asD(a: Any) = a.asInstanceOf[D] + | + | def t1(c: C) = asC(c) // eliminated + | def t2(c: C) = asO(c) // eliminated + | def t3(c: Object) = asC(c) // not elimianted + | def t4(c: C, d: D, b: Boolean) = asC(if (b) c else d) // not eliminated: lub of two non-equal reference types approximated with Object + | def t5(c: C, d: D, b: Boolean) = asO(if (b) c else d) + | def t6(c: C, cs: Array[C], b: Boolean) = asO(if (b) c else cs) + |} + |class D extends C + """.stripMargin + val List(c, _) = compile(code) + def casts(m: String) = getSingleMethod(c, m).instructions collect { case TypeOp(CHECKCAST, tp) => tp } + assertSameCode(getSingleMethod(c, "t1"), List(VarOp(ALOAD, 1), Op(ARETURN))) + assertSameCode(getSingleMethod(c, "t2"), List(VarOp(ALOAD, 1), Op(ARETURN))) + assertSameCode(getSingleMethod(c, "t3"), List(VarOp(ALOAD, 1), TypeOp(CHECKCAST, "C"), Op(ARETURN))) + assertEquals(casts("t4"), List("C")) + assertEquals(casts("t5"), Nil) + assertEquals(casts("t6"), Nil) + } + + @Test + def inlineFromSealed(): Unit = { + val code = + """sealed abstract class Foo { + | @inline def bar(x: Int) = x + 1 + |} + |object Foo { + | def mkFoo(): Foo = new Baz2 + |} + | + |object Baz1 extends Foo + |final class Baz2 extends Foo + | + |object Test { + | def f = Foo.mkFoo() bar 10 + |} + """.stripMargin + + val cls = compile(code) + val test = cls.find(_.name == "Test$").get + assertSameSummary(getSingleMethod(test, "f"), List( + GETSTATIC, "mkFoo", + BIPUSH, ISTORE, + IFNONNULL, ACONST_NULL, ATHROW, -1 /*label*/, + ILOAD, ICONST_1, IADD, IRETURN)) + } + + @Test // a test taken from the test suite for the 2.11 inliner + def oldInlineHigherOrderTest(): Unit = { + val code = + """class C { + | private var debug = false + | @inline private def ifelse[T](cond: => Boolean, ifPart: => T, elsePart: => T): T = if (cond) ifPart else elsePart + | final def t = ifelse(debug, 1, 2) + |} + """.stripMargin + val List(c) = compile(code) + + // box-unbox will clean it up + assertSameSummary(getSingleMethod(c, "t"), List( + ALOAD, "C$$$anonfun$1", IFEQ /*A*/, + "C$$$anonfun$2", IRETURN, + -1 /*A*/, "C$$$anonfun$3", IRETURN)) + } + + @Test + def inlineProject(): Unit = { + val codeA = "final class A { @inline def f = 1 }" + val codeB = "class B { def t(a: A) = a.f }" + // tests that no warning is emitted + val List(a, b) = compileClassesSeparately(List(codeA, codeB), extraArgs = "-Yopt:l:project -Yopt-warnings") + assertInvoke(getSingleMethod(b, "t"), "A", "f") + } + + @Test + def sd86(): Unit = { + val code = + """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, 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)) + } + + @Test + def sd140(): Unit = { + val code = + """trait T { @inline def f = 0 } + |trait U extends T { @inline override def f = 1 } + |trait V extends T { def m = 0 } + |final class K extends V with U { override def m = super[V].m } + |class C { def t = (new K).f } + """.stripMargin + val c :: _ = compile(code) + assertSameSummary(getSingleMethod(c, "t"), List(NEW, "<init>", ICONST_1, IRETURN)) // ICONST_1, U.f is inlined (not T.f) + } + + @Test + def inlineArrayForeach(): Unit = { + val code = + """class C { + | def consume(x: Int) = () + | def t(a: Array[Int]): Unit = a foreach consume + |} + """.stripMargin + val List(c) = compile(code) + val t = getSingleMethod(c, "t") + assertNoIndy(t) + assertInvoke(t, "C", "C$$$anonfun$1") + } } diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/MethodLevelOpts.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/MethodLevelOpts.scala deleted file mode 100644 index 5ef2458c0a..0000000000 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/MethodLevelOpts.scala +++ /dev/null @@ -1,92 +0,0 @@ -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.testing.AssertUtil._ - -import CodeGenTools._ -import scala.tools.partest.ASMConverters -import ASMConverters._ -import scala.tools.testing.ClearAfterClass - -object MethodLevelOpts extends ClearAfterClass.Clearable { - var methodOptCompiler = newCompiler(extraArgs = "-target:jvm-1.6 -Ybackend:GenBCode -Yopt:l:method") - def clear(): Unit = { methodOptCompiler = null } -} - -@RunWith(classOf[JUnit4]) -class MethodLevelOpts extends ClearAfterClass { - ClearAfterClass.stateToClear = MethodLevelOpts - - val methodOptCompiler = MethodLevelOpts.methodOptCompiler - - def wrapInDefault(code: Instruction*) = List(Label(0), LineNumber(1, Label(0))) ::: code.toList ::: List(Label(1)) - - @Test - def eliminateEmptyTry(): Unit = { - val code = "def f = { try {} catch { case _: Throwable => 0; () }; 1 }" - val warn = "a pure expression does nothing in statement position" - assertSameCode(singleMethodInstructions(methodOptCompiler)(code, allowMessage = _.msg contains warn), wrapInDefault(Op(ICONST_1), Op(IRETURN))) - } - - @Test - def cannotEliminateLoadBoxedUnit(): Unit = { - // the compiler inserts a boxed into the try block. it's therefore non-empty (and live) and not eliminated. - val code = "def f = { try {} catch { case _: Throwable => 0 }; 1 }" - val m = singleMethod(methodOptCompiler)(code) - assertTrue(m.handlers.length == 1) - assertSameCode(m.instructions.take(3), List(Label(0), LineNumber(1, Label(0)), Field(GETSTATIC, "scala/runtime/BoxedUnit", "UNIT", "Lscala/runtime/BoxedUnit;"))) - } - - @Test - def inlineThrowInCatchNotTry(): Unit = { - // the try block does not contain the `ATHROW` instruction, but in the catch block, `ATHROW` is inlined - val code = "def f(e: Exception) = throw { try e catch { case _: Throwable => e } }" - val m = singleMethod(methodOptCompiler)(code) - assertHandlerLabelPostions(m.handlers.head, m.instructions, 0, 3, 5) - assertSameCode(m.instructions, - wrapInDefault(VarOp(ALOAD, 1), Label(3), Op(ATHROW), Label(5), FrameEntry(4, List(), List("java/lang/Throwable")), Op(POP), VarOp(ALOAD, 1), Op(ATHROW)) - ) - } - - @Test - def inlineReturnInCatchNotTry(): Unit = { - val code = "def f: Int = return { try 1 catch { case _: Throwable => 2 } }" - // cannot inline the IRETURN into the try block (because RETURN may throw IllegalMonitorState) - val m = singleMethod(methodOptCompiler)(code) - assertHandlerLabelPostions(m.handlers.head, m.instructions, 0, 3, 5) - assertSameCode(m.instructions, - wrapInDefault(Op(ICONST_1), Label(3), Op(IRETURN), Label(5), FrameEntry(4, List(), List("java/lang/Throwable")), Op(POP), Op(ICONST_2), Op(IRETURN))) - } - - @Test - def simplifyJumpsInTryCatchFinally(): Unit = { - val code = - """def f: Int = - | try { - | return 1 - | } catch { - | case _: Throwable => - | return 2 - | } finally { - | return 2 - | // dead - | val x = try 10 catch { case _: Throwable => 11 } - | println(x) - | } - """.stripMargin - val m = singleMethod(methodOptCompiler)(code) - assertTrue(m.handlers.length == 2) - assertSameCode(m.instructions.dropNonOp, // drop line numbers and labels that are only used by line numbers - - // one single label left :-) - List(Op(ICONST_1), VarOp(ISTORE, 2), Jump(GOTO, Label(20)), Op(POP), Op(ICONST_2), VarOp(ISTORE, 2), Jump(GOTO, Label(20)), VarOp(ASTORE, 3), Op(ICONST_2), Op(IRETURN), Label(20), Op(ICONST_2), Op(IRETURN)) - ) - } -} diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/MethodLevelOptsTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/MethodLevelOptsTest.scala new file mode 100644 index 0000000000..dd7fbd9977 --- /dev/null +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/MethodLevelOptsTest.scala @@ -0,0 +1,762 @@ +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.ClassNode +import scala.tools.nsc.backend.jvm.AsmUtils._ +import scala.tools.testing.AssertUtil._ + +import CodeGenTools._ +import scala.tools.partest.ASMConverters +import ASMConverters._ +import scala.tools.testing.ClearAfterClass +import scala.collection.JavaConverters._ + +object MethodLevelOptsTest extends ClearAfterClass.Clearable { + var methodOptCompiler = newCompiler(extraArgs = "-Yopt:l:method") + def clear(): Unit = { methodOptCompiler = null } +} + +@RunWith(classOf[JUnit4]) +class MethodLevelOptsTest extends ClearAfterClass { + ClearAfterClass.stateToClear = MethodLevelOptsTest + + val methodOptCompiler = MethodLevelOptsTest.methodOptCompiler + + def wrapInDefault(code: Instruction*) = List(Label(0), LineNumber(1, Label(0))) ::: code.toList ::: List(Label(1)) + + def locals(c: ClassNode, m: String) = findAsmMethod(c, m).localVariables.asScala.toList.map(l => (l.name, l.index)).sortBy(_._2) + + @Test + def eliminateEmptyTry(): Unit = { + val code = "def f = { try {} catch { case _: Throwable => 0; () }; 1 }" + val warn = "a pure expression does nothing in statement position" + assertSameCode(singleMethodInstructions(methodOptCompiler)(code, allowMessage = _.msg contains warn), wrapInDefault(Op(ICONST_1), Op(IRETURN))) + } + + @Test + def eliminateLoadBoxedUnit(): Unit = { + // the compiler inserts a boxed into the try block. it's therefore non-empty (and live) and not eliminated. + val code = "def f = { try {} catch { case _: Throwable => 0 }; 1 }" + val m = singleMethod(methodOptCompiler)(code) + assertTrue(m.handlers.length == 0) + assertSameCode(m, List(Op(ICONST_1), Op(IRETURN))) + } + + @Test + def inlineThrowInCatchNotTry(): Unit = { + // the try block does not contain the `ATHROW` instruction, but in the catch block, `ATHROW` is inlined + val code = "def f(e: Exception) = throw { try e catch { case _: Throwable => e } }" + val m = singleMethod(methodOptCompiler)(code) + assertHandlerLabelPostions(m.handlers.head, m.instructions, 0, 3, 5) + assertSameCode(m.instructions, + wrapInDefault(VarOp(ALOAD, 1), Label(3), Op(ATHROW), Label(5), FrameEntry(4, List(), List("java/lang/Throwable")), Op(POP), VarOp(ALOAD, 1), Op(ATHROW)) + ) + } + + @Test + def inlineReturnInCatchNotTry(): Unit = { + val code = "def f: Int = return { try 1 catch { case _: Throwable => 2 } }" + // cannot inline the IRETURN into the try block (because RETURN may throw IllegalMonitorState) + val m = singleMethod(methodOptCompiler)(code) + assertHandlerLabelPostions(m.handlers.head, m.instructions, 0, 3, 5) + assertSameCode(m.instructions, + wrapInDefault(Op(ICONST_1), Label(3), Op(IRETURN), Label(5), FrameEntry(4, List(), List("java/lang/Throwable")), Op(POP), Op(ICONST_2), Op(IRETURN))) + } + + @Test + def simplifyJumpsInTryCatchFinally(): Unit = { + val code = + """def f: Int = + | try { + | return 1 + | } catch { + | case _: Throwable => + | return 2 + | } finally { + | return 3 + | // dead + | val x = try 10 catch { case _: Throwable => 11 } + | println(x) + | } + """.stripMargin + val m = singleMethod(methodOptCompiler)(code) + assertTrue(m.handlers.isEmpty) + assertSameCode(m, List(Op(ICONST_3), Op(IRETURN))) + } + + @Test + def nullStoreLoadElim(): Unit = { + // point of this test: we have two cleanups + // - remove `ACONST_NULL; ASTORE x` if x is otherwise not live + // - remove `ASTORE x; ALOAD x` if x is otherwise not live + // in the example below, we have `ACONST_NULL; ASTORE x; ALOAD x`. in this case the store-load + // should be removed (even though it looks like a null-store at first). + val code = + """class C { + | def t = { + | val x = null + | x.toString + | } + |} + """.stripMargin + val List(c) = compileClasses(methodOptCompiler)(code) + assertSameCode(getSingleMethod(c, "t"), List( + Op(ACONST_NULL), Invoke(INVOKEVIRTUAL, "java/lang/Object", "toString", "()Ljava/lang/String;", false), Op(ARETURN))) + } + + @Test + def deadStoreReferenceElim(): Unit = { + val code = + """class C { + | def t = { + | var a = "a" // assign to non-initialized, directly removed by dead store + | a = "b" // assign to initialized, replaced by null-store, which is then removed: the var is not live, the uses are null-store or store-load + | a = "c" + | a // store-load pair will be eliminated + | } + |} + """.stripMargin + val List(c) = compileClasses(methodOptCompiler)(code) + assertSameCode( + getSingleMethod(c, "t"), List(Ldc(LDC, "c"), Op(ARETURN))) + } + + @Test + def deadStoreReferenceKeepNull(): Unit = { + val code = + """class C { + | def t = { + | var a = "el" // this store is live, used in the println. + | println(a) + | a = "met" // since it's an ASTORE to a live variable, cannot elim the store (SI-5313), but store null instead. + | // so we get `LDC met; POP; ACONST_NULL; ASTORE 1`. the `LDC met; POP` is eliminated by push-pop. + | a = "zit" // this store is live, so we get `LDC zit; ASOTRE 1; ALOAD 1; ARETURN`. + | // we cannot eliminated the store-load sequence, because the local is live (again SI-5313). + | a + | } + |} + """.stripMargin + val List(c) = compileClasses(methodOptCompiler)(code) + + assertSameCode(getSingleMethod(c, "t"), List( + Ldc(LDC, "el"), VarOp(ASTORE, 1), + Field(GETSTATIC, "scala/Predef$", "MODULE$", "Lscala/Predef$;"), VarOp(ALOAD, 1), Invoke(INVOKEVIRTUAL, "scala/Predef$", "println", "(Ljava/lang/Object;)V", false), + Op(ACONST_NULL), VarOp(ASTORE, 1), + Ldc(LDC, "zit"), VarOp(ASTORE, 1), VarOp(ALOAD, 1), Op(ARETURN))) + } + + @Test + def elimUnusedTupleObjectStringBox(): Unit = { + val code = + """class C { + | def t(x: Int, y: Int): Int = { + | val a = (x, y) // Tuple2$mcII$sp + | val b = (a, y) // Tuple2 + | val c = (new Object, "krik", new String) // unused java/lang/Object, java/lang/String allocation and string constant is also eliminated + | val d = new java.lang.Integer(x) + | val e = new String(new Array[Char](23)) // array allocation not eliminated, as it may throw (negative size, SI-8601) + | val f = new scala.runtime.IntRef(11) + | x + y + | } + |} + """.stripMargin + val List(c) = compileClasses(methodOptCompiler)(code) + assertSameCode(getSingleMethod(c, "t"), List( + IntOp(BIPUSH, 23), IntOp(NEWARRAY, 5), Op(POP), VarOp(ILOAD, 1), VarOp(ILOAD, 2), Op(IADD), Op(IRETURN))) + } + + @Test + def noElimImpureConstructor(): Unit = { + val code = + """class C { + | def t(x: Int, y: Int): Int = { + | val a = new java.lang.Integer("nono") + | x + y + | } + |} + """.stripMargin + val List(c) = compileClasses(methodOptCompiler)(code) + assertSameCode(getSingleMethod(c, "t"), List( + TypeOp(NEW, "java/lang/Integer"), Ldc(LDC, "nono"), Invoke(INVOKESPECIAL, "java/lang/Integer", "<init>", "(Ljava/lang/String;)V", false), + VarOp(ILOAD, 1), VarOp(ILOAD, 2), Op(IADD), Op(IRETURN))) + } + + @Test + def elimUnusedBoxUnbox(): Unit = { + val code = + """class C { + | def t(a: Long): Int = { + | val t = 3 + a + | val u = a + t + | val v: Any = u // scala/runtime/BoxesRunTime.boxToLong + | + | val w = (v, a) // a Tuple2 (not specialized because first value is Any) + | // so calls scala/runtime/BoxesRunTime.boxToLong on the second value + | + | val x = v.asInstanceOf[Long] // scala/runtime/BoxesRunTime.unboxToLong + | + | val z = (java.lang.Long.valueOf(a), t) // java box call on the left, scala/runtime/BoxesRunTime.boxToLong on the right + | + | 0 + | } + |} + """.stripMargin + val List(c) = compileClasses(methodOptCompiler)(code) + assertSameCode(getSingleMethod(c, "t"), List(Op(ICONST_0), Op(IRETURN))) + } + + @Test + def elimUnusedClosure(): Unit = { + val code = + """class C { + | def t(x: Int, y: Int): Int = { + | val f = (a: Int) => a + x + y + | val g = (b: Int) => b - x + | val h = (s: String) => println(s) + | f(30) + | } + |} + """.stripMargin + val List(c) = compileClasses(methodOptCompiler)(code) + assertSameCode(getSingleMethod(c, "t"), List( + IntOp(BIPUSH, 30), VarOp(ISTORE, 3), // no constant propagation, so we keep the store (and load below) of a const + VarOp(ILOAD, 1), + VarOp(ILOAD, 2), + VarOp(ILOAD, 3), + Invoke(INVOKESTATIC, "C", "C$$$anonfun$1", "(III)I", false), Op(IRETURN))) + } + + @Test + def rewriteSpecializedClosureCall(): Unit = { + val code = + """class C { + | def t = { + | val f1 = (x: Int) => println(x) // int-unit specialization + | val f2 = (x: Int, y: Long) => x == y // int-long-boolean + | f1(1) + | f2(3, 4) + | } + |} + """.stripMargin + val List(c) = compileClasses(methodOptCompiler)(code) + val t = getSingleMethod(c, "t") + assert(!t.instructions.exists(_.opcode == INVOKEDYNAMIC), t) + } + + @Test + def boxUnboxPrimitive(): Unit = { + val code = + """class C { + | def t1 = { + | val a: Any = runtime.BoxesRunTime.boxToInteger(1) + | runtime.BoxesRunTime.unboxToInt(a) + 1 + | } + | + | // two box and two unbox operations + | def t2(b: Boolean) = { + | val a = if (b) (3l: Any) else 2l + | a.asInstanceOf[Long] + 1 + a.asInstanceOf[Long] + | } + | + | def t3(i: Integer): Int = i.asInstanceOf[Int] + | + | def t4(l: Long): Any = l + | + | def t5(i: Int): Int = { + | val b = Integer.valueOf(i) + | val c: Integer = i + | b.asInstanceOf[Int] + c.intValue + | } + | + | def t6: Long = { + | val y = new java.lang.Boolean(true) + | val i: Integer = if (y) new Integer(10) else 13 + | val j: java.lang.Long = 3l + | j + i + | } + | + | def t7: Int = { + | val a: Any = 3 + | a.asInstanceOf[Int] + a.asInstanceOf[Int] + | } + | + | def t8 = null.asInstanceOf[Int] + | + | def t9: Int = { + | val a = Integer.valueOf(10) + | val b = runtime.BoxesRunTime.unboxToInt(a) + | a + b + | } + | + | @noinline def escape(a: Any) = () + | + | // example E4 in BoxUnbox doc comment + | def t10: Int = { + | val a = Integer.valueOf(10) // int 10 is stored into local + | escape(a) + | a // no unbox, 10 is read from local + | } + | + | // the boxes here cannot be eliminated. see doc comment in BoxUnbox, example E1. + | def t11(b: Boolean): Int = { + | val i = Integer.valueOf(10) + | val j = Integer.valueOf(41) + | escape(i) // force rewrite method M1 (see doc in BoxUnbox) + | val res: Integer = if (b) i else j + | res.toInt // cannot be re-written to a local variable read - we don't know which local to read + | } + | + | // both boxes have a single unboxing consumer, and the escape. note that the escape does + | // NOT put the two boxes into the same set of rewrite operations: we can rewrite both + | // boxes with their unbox individually. in both cases the box also escapes, so method + | // M1 will keep the box around. + | def t12(b: Boolean): Int = { + | val i = Integer.valueOf(10) + | val j = Integer.valueOf(32) + | escape(if (b) i else j) // force method M1. the escape here is a consumer for both boxes + | if (b) i.toInt else j.toInt // both boxes (i, j) have their own unboxing consumer + | } + |} + """.stripMargin + + val List(c) = compileClasses(methodOptCompiler)(code) + + assertNoInvoke(getSingleMethod(c, "t1")) + assertNoInvoke(getSingleMethod(c, "t2")) + assertInvoke(getSingleMethod(c, "t3"), "scala/runtime/BoxesRunTime", "unboxToInt") + assertInvoke(getSingleMethod(c, "t4"), "scala/runtime/BoxesRunTime", "boxToLong") + assertNoInvoke(getSingleMethod(c, "t5")) + assertNoInvoke(getSingleMethod(c, "t6")) + assertNoInvoke(getSingleMethod(c, "t7")) + assertSameSummary(getSingleMethod(c, "t8"), List(ICONST_0, IRETURN)) + assertNoInvoke(getSingleMethod(c, "t9")) + // t10: no invocation of unbox + assertEquals(getSingleMethod(c, "t10").instructions collect { case Invoke(_, owner, name, _, _) => (owner, name) }, List( + ("java/lang/Integer", "valueOf"), + ("C", "escape"))) + + assertSameSummary(getSingleMethod(c, "t11"), List( + BIPUSH, "valueOf", ASTORE /*2*/, + BIPUSH, "valueOf", ASTORE /*3*/, + ALOAD /*0*/, ALOAD /*2*/, "escape", + ILOAD /*1*/, IFEQ /*L1*/, ALOAD /*2*/, GOTO /*L2*/, /*Label L1*/ -1, ALOAD /*3*/, /*Label L2*/ -1, + ASTORE /*4*/, GETSTATIC /*Predef*/, ALOAD /*4*/, "Integer2int", IRETURN)) + + // no unbox invocations + assertEquals(getSingleMethod(c, "t12").instructions collect { case Invoke(_, owner, name, _, _) => (owner, name) }, List( + ("java/lang/Integer", "valueOf"), + ("java/lang/Integer", "valueOf"), + ("C", "escape"))) + } + + @Test + def refEliminiation(): Unit = { + val code = + """class C { + | import runtime._ + | @noinline def escape(a: Any) = () + | + | def t1 = { // box eliminated + | val r = new IntRef(0) + | r.elem + | } + | + | def t2(b: Boolean) = { + | val r1 = IntRef.zero() // both eliminated + | val r2 = IntRef.create(1) + | val res: IntRef = if (b) r1 else r2 + | res.elem + | } + | + | def t3 = { + | val r = LongRef.create(10l) // eliminated + | r.elem += 3 + | r.elem + | } + | + | def t4(b: Boolean) = { + | val x = BooleanRef.create(false) // eliminated + | if (b) x.elem = true + | if (x.elem) "a" else "b" + | } + | + | def t5 = { + | val r = IntRef.create(10) // not eliminated: the box might be modified in the escape + | escape(r) + | r.elem + | } + | + | def t6(b: Boolean) = { + | val r1 = IntRef.zero() + | val r2 = IntRef.create(1) + | r1.elem = 39 + | val res: IntRef = if (b) r1 else r2 + | res.elem // boxes remain: can't rewrite this read, don't know which local + | } + |} + """.stripMargin + val List(c) = compileClasses(methodOptCompiler)(code) + assertSameSummary(getSingleMethod(c, "t1"), List(ICONST_0, IRETURN)) + assertNoInvoke(getSingleMethod(c, "t2")) + assertSameSummary(getSingleMethod(c, "t3"), List(LDC, LDC, LADD, LRETURN)) + assertNoInvoke(getSingleMethod(c, "t4")) + assertEquals(getSingleMethod(c, "t5").instructions collect { case Field(_, owner, name, _) => s"$owner.$name" }, + List("scala/runtime/IntRef.elem")) + assertEquals(getSingleMethod(c, "t6").instructions collect { case Field(op, owner, name, _) => s"$op $owner.$name" }, + List(s"$PUTFIELD scala/runtime/IntRef.elem", s"$GETFIELD scala/runtime/IntRef.elem")) + } + + @Test + def tupleElimination(): Unit = { + val code = + """class C { + | def t1(b: Boolean) = { + | val t = ("hi", "fish") + | if (b) t._1 else t._2 + | } + | + | def t2 = { + | val t = (1, 3) // specialized tuple + | t._1 + t._2 // specialized accessors (_1$mcII$sp) + | } + | + | def t3 = { + | // boxed before tuple creation, a non-specialized tuple is created + | val t = (new Integer(3), Integer.valueOf(4)) + | t._1 + t._2 // invokes the generic `_1` / `_2` getters, both values unboxed by Integer2int + | } + | + | def t4: Any = { + | val t = (3, 3) // specialized tuple is created, ints are not boxed + | (t: Tuple2[Any, Any])._1 // when eliminating the _1 call, need to insert a boxing operation + | } + | + | // the inverse of t4 also happens: an Tuple[Integer] where _1$mcI$sp is invoked. In this + | // case, an unbox operation needs to be added when eliminating the extraction. The only + | // way I found to test this is with an inlined generic method, see InlinerTest.tupleElimination. + | def tpl[A, B](a: A, b: B) = (a, b) + | def t5: Int = tpl(1, 2)._1 // invokes _1$mcI$sp + | + | def t6 = { + | val (a, b) = (1, 2) + | a - b + | } + | + | def t7 = { + | // this example is more tricky to handle than it looks, see doc comment in BoxUnbox. + | val ((a, b), c) = ((1, 2), 3) + | a + b + c + | } + | + | def t8 = { + | val ((a, b), (c, d)) = ((1, 2), (3, Integer.valueOf(10))) + | a + b + c + d + | } + | + | def t9(a: Int, b: Int) = (a, b) match { // tuple is optimized away + | case (x, y) if x == y => 0 + | case (x, y) => x + y + | } + |} + """.stripMargin + val List(c) = compileClasses(methodOptCompiler)(code) + assertNoInvoke(getSingleMethod(c, "t1")) + assertSameSummary(getSingleMethod(c, "t2"), List(ICONST_1, ICONST_3, IADD, IRETURN)) + assertSameSummary(getSingleMethod(c, "t3"), List(ICONST_3, ICONST_4, IADD, IRETURN)) + assertSameSummary(getSingleMethod(c, "t4"), List(ICONST_3, "boxToInteger", ARETURN)) + assertEquals(getSingleMethod(c, "t5").instructions collect { case Invoke(_, owner, name, _, _) => (owner, name) }, List( + ("scala/runtime/BoxesRunTime", "boxToInteger"), + ("scala/runtime/BoxesRunTime", "boxToInteger"), + ("C", "tpl"), + ("scala/Tuple2", "_1$mcI$sp"))) + assertSameSummary(getSingleMethod(c, "t6"), List(ICONST_1, ICONST_2, ISUB, IRETURN)) + assertSameSummary(getSingleMethod(c, "t7"), List( + ICONST_1, ICONST_2, ISTORE, ISTORE, + ICONST_3, ISTORE, + ILOAD, ILOAD, IADD, ILOAD, IADD, IRETURN)) + assertNoInvoke(getSingleMethod(c, "t8")) + assertNoInvoke(getSingleMethod(c, "t9")) + } + + @Test + def nullnessOpts(): Unit = { + val code = + """class C { + | def t1 = { + | val a = new C + | if (a == null) + | println() // eliminated + | a + | } + | + | def t2 = null.asInstanceOf[Long] // replaced by zero value + | + | def t3 = { + | val t = (1, 3) + | val a = null + | if (t ne a) t._1 + | else throw new Error() + | } + | + | def t4 = { + | val i = Integer.valueOf(1) + | val a = null + | if (i eq a) throw new Error() + | else i.toInt + | } + | + | def t5 = { + | val i = runtime.DoubleRef.zero() + | if (i == null) throw new Error() + | else i.elem + | } + | + | def t6 = { + | var a = null + | var i = null + | a = i // eliminated (store of null to variable that is already null) + | a // replaced by ACONST_NULL (load of variable that is known null) + | } + | + | def t7 = { + | val a = null + | a.isInstanceOf[String] // eliminated, replaced by 0 (null.isInstanceOf is always false) + | } + |} + """.stripMargin + val List(c) = compileClasses(methodOptCompiler)(code) + assertSameSummary(getSingleMethod(c, "t1"), List(NEW, DUP, "<init>", ARETURN)) + assertSameCode(getSingleMethod(c, "t2"), List(Op(LCONST_0), Op(LRETURN))) + assertSameCode(getSingleMethod(c, "t3"), List(Op(ICONST_1), Op(IRETURN))) + assertSameCode(getSingleMethod(c, "t4"), List(Op(ICONST_1), Op(IRETURN))) + assertSameCode(getSingleMethod(c, "t5"), List(Op(DCONST_0), Op(DRETURN))) + assertSameCode(getSingleMethod(c, "t6"), List(Op(ACONST_NULL), Op(ARETURN))) + assertSameCode(getSingleMethod(c, "t7"), List(Op(ICONST_0), Op(IRETURN))) + } + + @Test + def elimRedundantNullCheck(): Unit = { + val code = + """class C { + | def t(x: Object) = { + | val bool = x == null + | if (x != null) 1 else 0 + | } + |} + """.stripMargin + val List(c) = compileClasses(methodOptCompiler)(code) + assertSameCode( + getSingleMethod(c, "t"), List( + VarOp(ALOAD, 1), Jump(IFNULL, Label(6)), Op(ICONST_1), Op(IRETURN), Label(6), Op(ICONST_0), Op(IRETURN))) + } + + @Test + def t5313(): Unit = { + val code = + """class C { + | def randomBoolean = scala.util.Random.nextInt % 2 == 0 + | + | // 3 stores to kept1 (slot 1), 1 store to result (slot 2) + | def t1 = { + | var kept1 = new Object + | val result = new java.lang.ref.WeakReference(kept1) + | kept1 = null // we can't eliminate this assignment because result can observe + | // when the object has no more references. See SI-5313 + | kept1 = new Object // could eliminate this one with a more elaborate analysis (we know it contains null) + | // however, such is not implemented: if a var is live, then stores are kept. + | result + | } + | + | // only two variables are live: kept2 and kept3. they end up on slots 1 and 2. + | // kept2 has 2 stores, kept3 has 1 store. + | def t2 = { + | var erased2 = null // we can eliminate this store because it's never used + | val erased3 = erased2 // and this + | var erased4 = erased2 // and this + | val erased5 = erased4 // and this + | var kept2: Object = new Object // ultimately can't be eliminated + | while(randomBoolean) { + | val kept3 = kept2 + | kept2 = null // this can't, because it clobbers kept2, which is used + | erased4 = null // safe to eliminate + | println(kept3) + | } + | 0 + | } + | + | def t3 = { + | var kept4 = new Object // have to keep, it's used + | try + | println(kept4) + | catch { + | case _ : Throwable => kept4 = null // have to keep, it clobbers kept4 which is used + | } + | 0 + | } + | + | def t4 = { + | var kept5 = new Object + | print(kept5) + | kept5 = null // can't eliminate it's a clobber and it's used + | print(kept5) + | kept5 = null // eliminated by nullness analysis (store null to a local that is known to be null) + | 0 + | } + | + | def t5 = { + | while(randomBoolean) { + | var kept6: AnyRef = null // not used, but have to keep because it clobbers the next used store + | // on the back edge of the loop + | kept6 = new Object // used + | println(kept6) + | } + | 0 + | } + |} + """.stripMargin + + val List(c) = compileClasses(methodOptCompiler)(code) + def stores(m: String) = getSingleMethod(c, m).instructions.filter(_.opcode == ASTORE) + + assertEquals(locals(c, "t1"), List(("this",0), ("kept1",1), ("result",2))) + assert(stores("t1") == List(VarOp(ASTORE, 1), VarOp(ASTORE, 2), VarOp(ASTORE, 1), VarOp(ASTORE, 1)), + textify(findAsmMethod(c, "t1"))) + + assertEquals(locals(c, "t2"), List(("this",0), ("kept2",1), ("kept3",2))) + assert(stores("t2") == List(VarOp(ASTORE, 1), VarOp(ASTORE, 2), VarOp(ASTORE, 1)), + textify(findAsmMethod(c, "t2"))) + + assertEquals(locals(c, "t3"), List(("this",0), ("kept4",1))) + assert(stores("t3") == List(VarOp(ASTORE, 1), VarOp(ASTORE, 1)), + textify(findAsmMethod(c, "t3"))) + + assertEquals(locals(c, "t4"), List(("this",0), ("kept5",1))) + assert(stores("t4") == List(VarOp(ASTORE, 1), VarOp(ASTORE, 1)), + textify(findAsmMethod(c, "t4"))) + + assertEquals(locals(c, "t5"), List(("this",0), ("kept6",1))) + assert(stores("t5") == List(VarOp(ASTORE, 1), VarOp(ASTORE, 1)), + textify(findAsmMethod(c, "t5"))) + } + + @Test + def testCpp(): Unit = { + // copied from an old test (run/test-cpp.scala) + val code = + """class C { + | import scala.util.Random._ + | + | def t1(x: Int) = { + | val y = x + | println(y) + | } + | + | def t2 = { + | val x = 2 + | val y = x + | println(y) + | } + | + | def t3 = { + | val x = this + | val y = x + | println(y) + | } + | + | def f = nextInt + | + | def t4 = { + | val x = f + | val y = x + | println(y) + | } + | + | def t5 = { + | var x = nextInt + | var y = x + | println(y) + | + | y = nextInt + | x = y + | println(x) + | } + |} + """.stripMargin + + val List(c) = compileClasses(methodOptCompiler)(code) + assertEquals(locals(c, "t1"), List(("this", 0), ("x", 1))) + + assertEquals(locals(c, "t2"), List(("this", 0), ("x", 1))) + // we don't have constant propagation (yet). + // the local var can't be optimized as a store;laod sequence, there's a GETSTATIC between the two + assertSameSummary(getSingleMethod(c, "t2"), List( + ICONST_2, ISTORE, GETSTATIC, ILOAD, "boxToInteger", "println", RETURN)) + + assertEquals(locals(c, "t3"), List(("this", 0))) + assertEquals(locals(c, "t4"), List(("this", 0), ("x", 1))) + assertEquals(locals(c, "t5"), List(("this", 0), ("x", 1))) + } + + @Test + def t7006(): Unit = { + val code = + """class C { + | def t: Unit = { + | try { + | val x = 3 + | } finally { + | print("hello") + | } + | while(true) { } + | } + |} + """.stripMargin + val List(c) = compileClasses(methodOptCompiler)(code) + val t = getSingleMethod(c, "t") + assertEquals(t.handlers, Nil) + assertEquals(locals(c, "t"), List(("this", 0))) + assertSameSummary(t, List(GETSTATIC, LDC, "print", -1, GOTO)) + } + + @Test + def booleanOrderingCompare(): Unit = { + val code = + """class C { + | def compare(x: Boolean, y: Boolean) = (x, y) match { + | case (false, true) => -1 + | case (true, false) => 1 + | case _ => 0 + | } + |} + """.stripMargin + val List(c) = compileClasses(methodOptCompiler)(code) + assertNoInvoke(getSingleMethod(c, "compare")) + } + + @Test + def t8790(): Unit = { + val code = + """class C { + | def t(x: Int, y: Int): String = (x, y) match { + | case (7, 8) => "a" + | case _ => "b" + | } + |} + """.stripMargin + val List(c) = compileClasses(methodOptCompiler)(code) + + assertSameSummary(getSingleMethod(c, "t"), List( + BIPUSH, ILOAD, IF_ICMPNE, + BIPUSH, ILOAD, IF_ICMPNE, + LDC, ASTORE, GOTO, + -1, LDC, ASTORE, + -1, ALOAD, ARETURN)) + } +} 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 f8e887426b..8dd23ec3ce 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala @@ -9,26 +9,43 @@ import scala.tools.asm.Opcodes._ import org.junit.Assert._ import CodeGenTools._ +import scala.tools.asm.tree.ClassNode import scala.tools.nsc.backend.jvm.BTypes.{MethodInlineInfo, InlineInfo} import scala.tools.partest.ASMConverters import ASMConverters._ -import scala.collection.convert.decorateAsScala._ +import scala.collection.JavaConverters._ +import scala.tools.testing.ClearAfterClass -object ScalaInlineInfoTest { - var compiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:l:none") +object ScalaInlineInfoTest extends ClearAfterClass.Clearable { + var compiler = newCompiler(extraArgs = "-Yopt:l:none") def clear(): Unit = { compiler = null } } @RunWith(classOf[JUnit4]) -class ScalaInlineInfoTest { - val compiler = newCompiler() +class ScalaInlineInfoTest extends ClearAfterClass { + ClearAfterClass.stateToClear = ScalaInlineInfoTest + val compiler = ScalaInlineInfoTest.compiler + + def inlineInfo(c: ClassNode): InlineInfo = c.attrs.asScala.collect({ case a: InlineInfoAttribute => a.inlineInfo }).head + + def mapDiff[A, B](a: Map[A, B], b: Map[A, B]) = { + val r = new StringBuilder + for ((a, av) <- a) { + if (!b.contains(a)) r.append(s"missing in b: $a\n") + else if (av != b(a)) r.append(s"different for $a: $av != ${b(a)}\n") + } + for (b <- b.keys.toList diff a.keys.toList) { + r.append(s"missing in a: $b\n") + } + r.toString + } @Test def traitMembersInlineInfo(): Unit = { val code = """trait T { | def f1 = 1 // concrete method - | private def f2 = 1 // implOnly method (does not end up in the interface) + | private def f2 = 1 // default method only (not in subclass) | def f3 = { | def nest = 0 // nested method (does not end up in the interface) | nest @@ -38,13 +55,13 @@ class ScalaInlineInfoTest { | def f4 = super.toString // super accessor | | object O // module accessor (method is generated) - | def f5 = { + | final def f5 = { | object L { val x = 0 } // nested module (just flattened out) | L.x | } | | @noinline - | def f6: Int // abstract method (not in impl class) + | def f6: Int // abstract method | | // fields | @@ -55,31 +72,111 @@ class ScalaInlineInfoTest { | | final val x5 = 0 |} + |class C extends T { + | def f6 = 0 + | var x3 = 0 + |} """.stripMargin - val cs @ List(t, tl, to, tCls) = compileClasses(compiler)(code) - val List(info) = t.attrs.asScala.collect({ case a: InlineInfoAttribute => a.inlineInfo }).toList - val expect = InlineInfo( - None, // self type + val cs @ List(c, t, tl, to) = compileClasses(compiler)(code) + val infoT = inlineInfo(t) + val expectT = InlineInfo ( false, // final class + None, // not a sam Map( - ("O()LT$O$;", MethodInlineInfo(true, false,false,false)), - ("T$$super$toString()Ljava/lang/String;",MethodInlineInfo(false,false,false,false)), - ("T$_setter_$x1_$eq(I)V", MethodInlineInfo(false,false,false,false)), - ("f1()I", MethodInlineInfo(false,true, false,false)), - ("f3()I", MethodInlineInfo(false,true, false,false)), - ("f4()Ljava/lang/String;", MethodInlineInfo(false,true, true, false)), - ("f5()I", MethodInlineInfo(false,true, false,false)), - ("f6()I", MethodInlineInfo(false,false,false,true )), - ("x1()I", MethodInlineInfo(false,false,false,false)), - ("x3()I", MethodInlineInfo(false,false,false,false)), - ("x3_$eq(I)V", MethodInlineInfo(false,false,false,false)), - ("x4()I", MethodInlineInfo(false,false,false,false)), - ("x5()I", MethodInlineInfo(true, false,false,false)), - ("y2()I", MethodInlineInfo(false,false,false,false)), - ("y2_$eq(I)V", MethodInlineInfo(false,false,false,false))), + ("O()LT$O$;", MethodInlineInfo(true ,false,false)), // the accessor is abstract in bytecode, but still effectivelyFinal because there's no (late)DEFERRED flag, https://github.com/scala/scala-dev/issues/126 + ("T$$super$toString()Ljava/lang/String;", MethodInlineInfo(true ,false,false)), + ("T$_setter_$x1_$eq(I)V", MethodInlineInfo(false,false,false)), + ("f1()I", MethodInlineInfo(false,false,false)), + ("f2()I", MethodInlineInfo(true, false,false)), + ("f3()I", MethodInlineInfo(false,false,false)), + ("f4()Ljava/lang/String;", MethodInlineInfo(false,true, false)), + ("f5()I", MethodInlineInfo(true ,false,false)), + ("f6()I", MethodInlineInfo(false,false,true )), + ("x1()I", MethodInlineInfo(false,false,false)), + ("y2()I", MethodInlineInfo(false,false,false)), + ("y2_$eq(I)V", MethodInlineInfo(false,false,false)), + ("x3()I", MethodInlineInfo(false,false,false)), + ("x3_$eq(I)V", MethodInlineInfo(false,false,false)), + ("x4()I", MethodInlineInfo(false,false,false)), + ("x5()I", MethodInlineInfo(true, false,false)), + ("L$lzycompute$1(Lscala/runtime/VolatileObjectRef;)LT$L$2$;", MethodInlineInfo(true, false,false)), + ("L$1(Lscala/runtime/VolatileObjectRef;)LT$L$2$;", MethodInlineInfo(true ,false,false)), + ("nest$1()I", MethodInlineInfo(true, false,false)), + ("$init$()V", MethodInlineInfo(false,false,false))), None // warning ) - assert(info == expect, info) + + assert(infoT == expectT, mapDiff(expectT.methodInfos, infoT.methodInfos) + infoT) + + val infoC = inlineInfo(c) + val expectC = InlineInfo(false, None, Map( + "O()LT$O$;" -> 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), + "y2()I" -> MethodInlineInfo(false,false,false), + "y2_$eq(I)V" -> MethodInlineInfo(false,false,false), + "x3()I" -> MethodInlineInfo(false,false,false), + "x3_$eq(I)V" -> MethodInlineInfo(false,false,false), + "x4$lzycompute()I" -> MethodInlineInfo(true ,false,false), + "x4()I" -> MethodInlineInfo(false,false,false), + "x5()I" -> MethodInlineInfo(true ,false,false), + "T$$super$toString()Ljava/lang/String;" -> MethodInlineInfo(true ,false,false), + "<init>()V" -> MethodInlineInfo(false,false,false)), + None) + + assert(infoC == expectC, mapDiff(expectC.methodInfos, infoC.methodInfos) + infoC) + } + + @Test + def inlineInfoSam(): Unit = { + val code = + """trait C { // expected to be seen as sam: g(I)I + | def f = 0 + | def g(x: Int): Int + | val foo = "hi" + |} + |abstract class D { + | val biz: Int + |} + |trait T { // expected to be seen as sam: h(Ljava/lang/String;)I + | def h(a: String): Int + |} + |trait E extends T { // expected to be seen as sam: h(Ljava/lang/String;)I + | def hihi(x: Int) = x + |} + |class F extends T { + | def h(a: String) = 0 + |} + |trait U { + | def conc() = 10 + | def nullary: Int + |} + """.stripMargin + val cs = compileClasses(compiler)(code) + val sams = cs.map(c => (c.name, inlineInfo(c).sam)) + assertEquals(sams, + List( + ("C",Some("g(I)I")), + ("D",None), + ("E",Some("h(Ljava/lang/String;)I")), + ("F",None), + ("T",Some("h(Ljava/lang/String;)I")), + ("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)) } } diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/SimplifyJumpsTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/SimplifyJumpsTest.scala index a685ae7dd5..99acb318de 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/SimplifyJumpsTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/SimplifyJumpsTest.scala @@ -96,10 +96,22 @@ class SimplifyJumpsTest { instructionsFromMethod(method), List(VarOp(ILOAD, 1), Jump(IFLT, Label(3))) ::: rest.tail ) - // no label allowed between begin and rest. if there's another label, then there could be a - // branch that label. eliminating the GOTO would change the behavior. - val nonOptMethod = genMethod()(begin ::: Label(22) :: rest: _*) - assertFalse(LocalOptImpls.simplifyJumps(nonOptMethod)) + // branch over goto is OK even if there's a label in between, if that label is not a jump target + val withNonJumpTargetLabel = genMethod()(begin ::: Label(22) :: rest: _*) + assertTrue(LocalOptImpls.simplifyJumps(withNonJumpTargetLabel)) + assertSameCode( + instructionsFromMethod(withNonJumpTargetLabel), + List(VarOp(ILOAD, 1), Jump(IFLT, Label(3)), Label(22)) ::: rest.tail ) + + // if the Label(22) between IFGE and GOTO is the target of some jump, we cannot rewrite the IFGE + // and remove the GOTO: removing the GOTO would change semantics. However, the jump that targets + // Label(22) will be re-written (jump-chain collapsing), so in a second round, the IFGE is still + // rewritten to IFLT + val twoRounds = genMethod()(List(VarOp(ILOAD, 1), Jump(IFLE, Label(22))) ::: begin ::: Label(22) :: rest: _*) + assertTrue(LocalOptImpls.simplifyJumps(twoRounds)) + assertSameCode( + instructionsFromMethod(twoRounds), + List(VarOp(ILOAD, 1), Jump(IFLE, Label(3)), VarOp(ILOAD, 1), Jump(IFLT, Label(3)), Label(22)) ::: rest.tail ) } @Test @@ -167,6 +179,9 @@ class SimplifyJumpsTest { VarOp(ILOAD, 1), Jump(IFGE, Label(target)), + VarOp(ILOAD, 1), // some code to prevent rewriting the conditional jump + Op(IRETURN), + Label(4), Jump(GOTO, Label(3)), diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala index 902af7b7fa..0021a1784d 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala @@ -18,18 +18,14 @@ import scala.tools.testing.ClearAfterClass object UnreachableCodeTest extends ClearAfterClass.Clearable { // jvm-1.6 enables emitting stack map frames, which impacts the code generation wrt dead basic blocks, // see comment in BCodeBodyBuilder - var methodOptCompiler = newCompiler(extraArgs = "-target:jvm-1.6 -Ybackend:GenBCode -Yopt:l:method") - var dceCompiler = newCompiler(extraArgs = "-target:jvm-1.6 -Ybackend:GenBCode -Yopt:unreachable-code") - var noOptCompiler = newCompiler(extraArgs = "-target:jvm-1.6 -Ybackend:GenBCode -Yopt:l:none") - - // jvm-1.5 disables computing stack map frames, and it emits dead code as-is. note that this flag triggers a deprecation warning - var noOptNoFramesCompiler = newCompiler(extraArgs = "-target:jvm-1.5 -Ybackend:GenBCode -Yopt:l:none -deprecation") + var methodOptCompiler = newCompiler(extraArgs = "-Yopt:l:method") + var dceCompiler = newCompiler(extraArgs = "-Yopt:unreachable-code") + var noOptCompiler = newCompiler(extraArgs = "-Yopt:l:none") def clear(): Unit = { methodOptCompiler = null dceCompiler = null noOptCompiler = null - noOptNoFramesCompiler = null } } @@ -40,11 +36,10 @@ class UnreachableCodeTest extends ClearAfterClass { val methodOptCompiler = UnreachableCodeTest.methodOptCompiler val dceCompiler = UnreachableCodeTest.dceCompiler val noOptCompiler = UnreachableCodeTest.noOptCompiler - val noOptNoFramesCompiler = UnreachableCodeTest.noOptNoFramesCompiler def assertEliminateDead(code: (Instruction, Boolean)*): Unit = { val method = genMethod()(code.map(_._1): _*) - LocalOptImpls.removeUnreachableCodeImpl(method, "C") + dceCompiler.genBCode.bTypes.localOpt.removeUnreachableCodeImpl(method, "C") val nonEliminated = instructionsFromMethod(method) val expectedLive = code.filter(_._2).map(_._1).toList assertSameCode(nonEliminated, expectedLive) @@ -152,11 +147,6 @@ class UnreachableCodeTest extends ClearAfterClass { // Finally, instructions in the dead basic blocks are replaced by ATHROW, as explained in // a comment in BCodeBodyBuilder. assertSameCode(noDce.dropNonOp, List(Op(ICONST_1), Op(IRETURN), Op(ATHROW), Op(ATHROW))) - - // when NOT computing stack map frames, ASM's ClassWriter does not replace dead code by NOP/ATHROW - val warn = "target:jvm-1.5 is deprecated" - val noDceNoFrames = singleMethodInstructions(noOptNoFramesCompiler)(code, allowMessage = _.msg contains warn) - assertSameCode(noDceNoFrames.dropNonOp, List(Op(ICONST_1), Op(IRETURN), Op(ICONST_2), Op(IRETURN))) } @Test @@ -225,4 +215,50 @@ class UnreachableCodeTest extends ClearAfterClass { assertTrue(List(FrameEntry(F_FULL, List(INTEGER, DOUBLE, Label(3)), List("java/lang/Object", Label(4))), Label(3), Label(4)) === List(FrameEntry(F_FULL, List(INTEGER, DOUBLE, Label(1)), List("java/lang/Object", Label(3))), Label(1), Label(3))) } + + @Test + def loadNullNothingBytecode(): Unit = { + val code = + """class C { + | def nl: Null = null + | def nt: Nothing = throw new Error("") + | def cons(a: Any) = () + | + | def t1 = cons(null) + | def t2 = cons(nl) + | def t3 = cons(throw new Error("")) + | def t4 = cons(nt) + |} + """.stripMargin + val List(c) = compileClasses(noOptCompiler)(code) + + assertSameSummary(getSingleMethod(c, "nl"), List(ACONST_NULL, ARETURN)) + + assertSameSummary(getSingleMethod(c, "nt"), List( + NEW, DUP, LDC, "<init>", ATHROW)) + + assertSameSummary(getSingleMethod(c, "t1"), List( + ALOAD, ACONST_NULL, "cons", RETURN)) + + // GenBCode introduces POP; ACONST_NULL after loading an expression of type scala.runtime.Null$, + // see comment in BCodeBodyBuilder.adapt + assertSameSummary(getSingleMethod(c, "t2"), List( + ALOAD, ALOAD, "nl", POP, ACONST_NULL, "cons", RETURN)) + + // the bytecode generated by GenBCode is ... ATHROW; INVOKEVIRTUAL C.cons; RETURN + // the ASM classfile writer creates a new basic block (creates a label) right after the ATHROW + // and replaces all instructions by NOP*; ATHROW, see comment in BCodeBodyBuilder.adapt + // NOTE: DCE is enabled by default and gets rid of the redundant code (tested below) + assertSameSummary(getSingleMethod(c, "t3"), List( + ALOAD, NEW, DUP, LDC, "<init>", ATHROW, NOP, NOP, NOP, ATHROW)) + + // GenBCode introduces an ATHROW after the invocation of C.nt, see BCodeBodyBuilder.adapt + // NOTE: DCE is enabled by default and gets rid of the redundant code (tested below) + assertSameSummary(getSingleMethod(c, "t4"), List( + ALOAD, ALOAD, "nt", ATHROW, NOP, NOP, NOP, ATHROW)) + + val List(cDCE) = compileClasses(dceCompiler)(code) + assertSameSummary(getSingleMethod(cDCE, "t3"), List(ALOAD, NEW, DUP, LDC, "<init>", ATHROW)) + assertSameSummary(getSingleMethod(cDCE, "t4"), List(ALOAD, ALOAD, "nt", ATHROW)) + } } diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/UnusedLocalVariablesTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/UnusedLocalVariablesTest.scala index 769736669b..4f71df1822 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/UnusedLocalVariablesTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/UnusedLocalVariablesTest.scala @@ -15,7 +15,7 @@ import ASMConverters._ import scala.tools.testing.ClearAfterClass object UnusedLocalVariablesTest extends ClearAfterClass.Clearable { - var dceCompiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:unreachable-code") + var dceCompiler = newCompiler(extraArgs = "-Yopt:unreachable-code") def clear(): Unit = { dceCompiler = null } } diff --git a/test/junit/scala/tools/nsc/classpath/AggregateFlatClassPathTest.scala b/test/junit/scala/tools/nsc/classpath/AggregateClassPathTest.scala index 9a004d5e0e..a7aca31ee3 100644 --- a/test/junit/scala/tools/nsc/classpath/AggregateFlatClassPathTest.scala +++ b/test/junit/scala/tools/nsc/classpath/AggregateClassPathTest.scala @@ -10,6 +10,7 @@ import org.junit.runner.RunWith import org.junit.runners.JUnit4 import scala.reflect.io.VirtualFile import scala.tools.nsc.io.AbstractFile +import scala.tools.nsc.util.ClassPath /** * Tests whether AggregateFlatClassPath returns correct entries taken from @@ -17,14 +18,14 @@ import scala.tools.nsc.io.AbstractFile * (in the case of the repeated entry for a class or a source it returns the first one). */ @RunWith(classOf[JUnit4]) -class AggregateFlatClassPathTest { +class AggregateClassPathTest { - private class TestFlatClassPath extends FlatClassPath { + private abstract class TestClassPathBase extends ClassPath { override def packages(inPackage: String): Seq[PackageEntry] = unsupported override def sources(inPackage: String): Seq[SourceFileEntry] = unsupported override def classes(inPackage: String): Seq[ClassFileEntry] = unsupported - override def list(inPackage: String): FlatClassPathEntries = unsupported + override def list(inPackage: String): ClassPathEntries = unsupported override def findClassFile(name: String): Option[AbstractFile] = unsupported override def asClassPathStrings: Seq[String] = unsupported @@ -32,7 +33,7 @@ class AggregateFlatClassPathTest { override def asURLs: Seq[URL] = unsupported } - private case class TestClassPath(virtualPath: String, classesInPackage: EntryNamesInPackage*) extends TestFlatClassPath { + private case class TestClassPath(virtualPath: String, classesInPackage: EntryNamesInPackage*) extends TestClassPathBase { override def classes(inPackage: String): Seq[ClassFileEntry] = for { @@ -43,10 +44,10 @@ class AggregateFlatClassPathTest { override def sources(inPackage: String): Seq[SourceFileEntry] = Nil // we'll ignore packages - override def list(inPackage: String): FlatClassPathEntries = FlatClassPathEntries(Nil, classes(inPackage)) + override def list(inPackage: String): ClassPathEntries = ClassPathEntries(Nil, classes(inPackage)) } - private case class TestSourcePath(virtualPath: String, sourcesInPackage: EntryNamesInPackage*) extends TestFlatClassPath { + private case class TestSourcePath(virtualPath: String, sourcesInPackage: EntryNamesInPackage*) extends TestClassPathBase { override def sources(inPackage: String): Seq[SourceFileEntry] = for { @@ -57,7 +58,7 @@ class AggregateFlatClassPathTest { override def classes(inPackage: String): Seq[ClassFileEntry] = Nil // we'll ignore packages - override def list(inPackage: String): FlatClassPathEntries = FlatClassPathEntries(Nil, sources(inPackage)) + override def list(inPackage: String): ClassPathEntries = ClassPathEntries(Nil, sources(inPackage)) } private case class EntryNamesInPackage(inPackage: String)(val names: String*) @@ -88,7 +89,7 @@ class AggregateFlatClassPathTest { private def virtualFile(pathPrefix: String, inPackage: String, fileName: String, extension: String) = { val packageDirs = - if (inPackage == FlatClassPath.RootPackage) "" + if (inPackage == ClassPath.RootPackage) "" else inPackage.split('.').mkString("/", "/", "") new VirtualFile(fileName + extension, s"$pathPrefix$packageDirs/$fileName$extension") } @@ -101,12 +102,12 @@ class AggregateFlatClassPathTest { TestSourcePath(dir2, EntryNamesInPackage(pkg3)("J", "K", "L")) ) - AggregateFlatClassPath(partialClassPaths) + AggregateClassPath(partialClassPaths) } @Test def testGettingPackages: Unit = { - case class ClassPathWithPackages(packagesInPackage: EntryNamesInPackage*) extends TestFlatClassPath { + case class ClassPathWithPackages(packagesInPackage: EntryNamesInPackage*) extends TestClassPathBase { override def packages(inPackage: String): Seq[PackageEntry] = packagesInPackage.find(_.inPackage == inPackage).map(_.names).getOrElse(Nil) map PackageEntryImpl } @@ -115,7 +116,7 @@ class AggregateFlatClassPathTest { ClassPathWithPackages(EntryNamesInPackage(pkg1)("pkg1.c", "pkg1.b", "pkg1.a"), EntryNamesInPackage(pkg2)("pkg2.d", "pkg2.a", "pkg2.e")) ) - val cp = AggregateFlatClassPath(partialClassPaths) + val cp = AggregateClassPath(partialClassPaths) val packagesInPkg1 = Seq("pkg1.a", "pkg1.d", "pkg1.f", "pkg1.c", "pkg1.b") assertEquals(packagesInPkg1, cp.packages(pkg1).map(_.name)) @@ -156,7 +157,7 @@ class AggregateFlatClassPathTest { TestClassPath(dir4, EntryNamesInPackage(pkg2)("A", "H", "I")), TestClassPath(dir2, EntryNamesInPackage(pkg3)("J", "K", "L")) ) - val cp = AggregateFlatClassPath(partialClassPaths) + val cp = AggregateClassPath(partialClassPaths) val sourcesInPkg1 = Seq(sourceFileEntry(dir2, pkg1, "C"), sourceFileEntry(dir2, pkg1, "B"), @@ -190,7 +191,7 @@ class AggregateFlatClassPathTest { ) assertEquals(classesAndSourcesInPkg1, cp.list(pkg1).classesAndSources) - assertEquals(FlatClassPathEntries(Nil, Nil), cp.list(nonexistingPkg)) + assertEquals(ClassPathEntries(Nil, Nil), cp.list(nonexistingPkg)) } @Test diff --git a/test/junit/scala/tools/nsc/classpath/FlatClassPathResolverTest.scala b/test/junit/scala/tools/nsc/classpath/PathResolverBaseTest.scala index 5dee488285..d3d4289d8b 100644 --- a/test/junit/scala/tools/nsc/classpath/FlatClassPathResolverTest.scala +++ b/test/junit/scala/tools/nsc/classpath/PathResolverBaseTest.scala @@ -9,20 +9,17 @@ import org.junit._ import org.junit.rules.TemporaryFolder import org.junit.runner.RunWith import org.junit.runners.JUnit4 -import scala.annotation.tailrec -import scala.tools.nsc.io.AbstractFile import scala.tools.nsc.util.ClassPath import scala.tools.nsc.Settings -import scala.tools.util.FlatClassPathResolver import scala.tools.util.PathResolver @RunWith(classOf[JUnit4]) -class FlatClassPathResolverTest { +class PathResolverBaseTest { val tempDir = new TemporaryFolder() - private val packagesToTest = List(FlatClassPath.RootPackage, "scala", "scala.reflect", "scala.reflect.io") - private val classFilesToFind = List("scala.tools.util.FlatClassPathResolver", + private val packagesToTest = List(ClassPath.RootPackage, "scala", "scala.reflect", "scala.reflect.io") + private val classFilesToFind = List("scala.tools.util.PathResolver", "scala.reflect.io.AbstractFile", "scala.collection.immutable.List", "scala.Option", @@ -60,7 +57,7 @@ class FlatClassPathResolverTest { def deleteTempDir: Unit = tempDir.delete() private def createFlatClassPath(settings: Settings) = - new FlatClassPathResolver(settings).result + new PathResolver(settings).result @Test def testEntriesFromListOperationAgainstSeparateMethods: Unit = { @@ -70,7 +67,7 @@ class FlatClassPathResolverTest { val packages = classPath.packages(inPackage) val classes = classPath.classes(inPackage) val sources = classPath.sources(inPackage) - val FlatClassPathEntries(packagesFromList, classesAndSourcesFromList) = classPath.list(inPackage) + val ClassPathEntries(packagesFromList, classesAndSourcesFromList) = classPath.list(inPackage) val packageNames = packages.map(_.name).sorted val packageNamesFromList = packagesFromList.map(_.name).sorted @@ -96,52 +93,6 @@ class FlatClassPathResolverTest { } @Test - def testCreatedEntriesAgainstRecursiveClassPath: Unit = { - val flatClassPath = createFlatClassPath(settings) - val recursiveClassPath = new PathResolver(settings).result - - def compareEntriesInPackage(inPackage: String): Unit = { - - @tailrec - def traverseToPackage(packageNameParts: Seq[String], cp: ClassPath[AbstractFile]): ClassPath[AbstractFile] = { - packageNameParts match { - case Nil => cp - case h :: t => - cp.packages.find(_.name == h) match { - case Some(nestedCp) => traverseToPackage(t, nestedCp) - case _ => throw new Exception(s"There's no package $inPackage in recursive classpath - error when searching for '$h'") - } - } - } - - val packageNameParts = if (inPackage == FlatClassPath.RootPackage) Nil else inPackage.split('.').toList - val recursiveClassPathInPackage = traverseToPackage(packageNameParts, recursiveClassPath) - - val flatCpPackages = flatClassPath.packages(inPackage).map(_.name) - val pkgPrefix = PackageNameUtils.packagePrefix(inPackage) - val recursiveCpPackages = recursiveClassPathInPackage.packages.map(pkgPrefix + _.name) - assertEquals(s"Packages in package '$inPackage' on flat cp should be the same as on the recursive cp", - recursiveCpPackages, flatCpPackages) - - val flatCpSources = flatClassPath.sources(inPackage).map(_.name).sorted - val recursiveCpSources = recursiveClassPathInPackage.classes - .filter(_.source.nonEmpty) - .map(_.name).sorted - assertEquals(s"Source entries in package '$inPackage' on flat cp should be the same as on the recursive cp", - recursiveCpSources, flatCpSources) - - val flatCpClasses = flatClassPath.classes(inPackage).map(_.name).sorted - val recursiveCpClasses = recursiveClassPathInPackage.classes - .filter(_.binary.nonEmpty) - .map(_.name).sorted - assertEquals(s"Class entries in package '$inPackage' on flat cp should be the same as on the recursive cp", - recursiveCpClasses, flatCpClasses) - } - - packagesToTest foreach compareEntriesInPackage - } - - @Test def testFindClassFile: Unit = { val classPath = createFlatClassPath(settings) classFilesToFind foreach { className => diff --git a/test/junit/scala/tools/nsc/settings/ScalaVersionTest.scala b/test/junit/scala/tools/nsc/settings/ScalaVersionTest.scala index 77a2da828e..3717f80362 100644 --- a/test/junit/scala/tools/nsc/settings/ScalaVersionTest.scala +++ b/test/junit/scala/tools/nsc/settings/ScalaVersionTest.scala @@ -13,6 +13,57 @@ class ScalaVersionTest { @Test def versionUnparse() { val v = "2.11.3" - assertEquals(ScalaVersion(v).unparse, v) + assertEquals(v, ScalaVersion(v).unparse) + assertEquals("2.11.3-RC4", ScalaVersion("2.11.3-rc4").unparse) + } + + // SI-9167 + @Test def `version parses with rigor`() { + import settings.{ SpecificScalaVersion => V } + import ScalaVersion._ + + // no-brainers + assertEquals(V(2,11,7,Final), ScalaVersion("2.11.7")) + assertEquals(V(2,11,7,Final), ScalaVersion("2.11.7-FINAL")) + assertEquals(V(2,11,7,Milestone(3)), ScalaVersion("2.11.7-M3")) + assertEquals(V(2,11,7,RC(3)), ScalaVersion("2.11.7-RC3")) + assertEquals(V(2,11,7,Development("devbuild")), ScalaVersion("2.11.7-devbuild")) + + // partial-brainers + assertEquals(V(2,11,7,Milestone(3)), ScalaVersion("2.11.7-m3")) + assertEquals(V(2,11,7,RC(3)), ScalaVersion("2.11.7-rc3")) + assertEquals(V(2,11,7,Development("maybegood")), ScalaVersion("2.11.7-maybegood")) + assertEquals(V(2,11,7,Development("RCCola")), ScalaVersion("2.11.7-RCCola")) + assertEquals(V(2,11,7,Development("RC1.5")), ScalaVersion("2.11.7-RC1.5")) + assertEquals(V(2,11,7,Development("")), ScalaVersion("2.11.7-")) + assertEquals(V(2,11,7,Development("0.5")), ScalaVersion("2.11.7-0.5")) + assertEquals(V(2,11,7,Development("devbuild\nSI-9167")), ScalaVersion("2.11.7-devbuild\nSI-9167")) + assertEquals(V(2,11,7,Development("final")), ScalaVersion("2.11.7-final")) + + // oh really + assertEquals(NoScalaVersion, ScalaVersion("none")) + assertEquals(AnyScalaVersion, ScalaVersion("any")) + + assertThrows[NumberFormatException] { ScalaVersion("2.11.7.2") } + assertThrows[NumberFormatException] { ScalaVersion("2.11.7.beta") } + assertThrows[NumberFormatException] { ScalaVersion("2.x.7") } + assertThrows[NumberFormatException] { ScalaVersion("2.-11.7") } + assertThrows[NumberFormatException] { ScalaVersion("2. ") } + assertThrows[NumberFormatException] { ScalaVersion("2.1 .7") } + assertThrows[NumberFormatException] { ScalaVersion("2.") } + assertThrows[NumberFormatException] { ScalaVersion("2..") } + assertThrows[NumberFormatException] { ScalaVersion("2...") } + assertThrows[NumberFormatException] { ScalaVersion("2-") } + assertThrows[NumberFormatException] { ScalaVersion("2-.") } // scalacheck territory + assertThrows[NumberFormatException] { ScalaVersion("any.7") } + + assertThrows[NumberFormatException] ( ScalaVersion("2.11-ok"), _ == + "Bad version (2.11-ok) not major[.minor[.revision[-suffix]]]" ) + + } + + // SI-9377 + @Test def `missing version is as good as none`() { + assertEquals(NoScalaVersion, ScalaVersion("")) } } diff --git a/test/junit/scala/tools/nsc/settings/SettingsTest.scala b/test/junit/scala/tools/nsc/settings/SettingsTest.scala index 96f83c4c2f..01a2351011 100644 --- a/test/junit/scala/tools/nsc/settings/SettingsTest.scala +++ b/test/junit/scala/tools/nsc/settings/SettingsTest.scala @@ -26,16 +26,16 @@ class SettingsTest { assertThrows[IllegalArgumentException](check("-Ytest-setting:rubbish")) } - @Test def userSettingsHavePrecedenceOverOptimize() { + @Test def userSettingsHavePrecedenceOverExperimental() { def check(args: String*): MutableSettings#BooleanSetting = { val s = new MutableSettings(msg => throw new IllegalArgumentException(msg)) val (ok, residual) = s.processArguments(args.toList, processAll = true) assert(residual.isEmpty) - s.inline // among -optimize + s.YmethodInfer // among -Xexperimental } - assertTrue(check("-optimise").value) - assertFalse(check("-optimise", "-Yinline:false").value) - assertFalse(check("-Yinline:false", "-optimise").value) + assertTrue(check("-Xexperimental").value) + assertFalse(check("-Xexperimental", "-Yinfer-argument-types:false").value) + assertFalse(check("-Yinfer-argument-types:false", "-Xexperimental").value) } // for the given args, select the desired setting @@ -172,12 +172,12 @@ class SettingsTest { assert(residual.isEmpty) assertTrue(s.source.value == ScalaVersion(expected)) } - check(expected = "2.11.0") // default + check(expected = "2.12.0") // default check(expected = "2.11.0", "-Xsource:2.11") check(expected = "2.10", "-Xsource:2.10.0") check(expected = "2.12", "-Xsource:2.12") assertThrows[IllegalArgumentException](check(expected = "2.11", "-Xsource"), _ == "-Xsource requires an argument, the syntax is -Xsource:<version>") assertThrows[IllegalArgumentException](check(expected = "2.11", "-Xsource", "2.11"), _ == "-Xsource requires an argument, the syntax is -Xsource:<version>") - assertThrows[IllegalArgumentException](check(expected = "2.11", "-Xsource:2.invalid"), _ contains "There was a problem parsing 2.invalid") + assertThrows[IllegalArgumentException](check(expected = "2.11", "-Xsource:2.invalid"), _ contains "Bad version (2.invalid)") } } diff --git a/test/junit/scala/tools/nsc/symtab/FlagsTest.scala b/test/junit/scala/tools/nsc/symtab/FlagsTest.scala index fc0e8b0f6b..96eae38011 100644 --- a/test/junit/scala/tools/nsc/symtab/FlagsTest.scala +++ b/test/junit/scala/tools/nsc/symtab/FlagsTest.scala @@ -33,7 +33,6 @@ class FlagsTest { def testTimedFlags(): Unit = { testLate(lateDEFERRED, _.isDeferred) testLate(lateFINAL, _.isFinal) - testLate(lateINTERFACE, _.isInterface) testLate(lateMETHOD, _.isMethod) testLate(lateMODULE, _.isModule) testNot(PROTECTED | notPROTECTED, _.isProtected) @@ -86,4 +85,11 @@ class FlagsTest { assertEquals(withFlagMask(AllFlags)(sym.setFlag(lateFlags).flags), lateFlags | lateable) } + + @Test + def javaClassMirrorAnnotationFlag(): Unit = { + import scala.reflect.runtime.universe._ + val dep = typeOf[java.lang.Deprecated].typeSymbol + assertTrue(dep.isJavaAnnotation && dep.isJava) + } } diff --git a/test/junit/scala/tools/nsc/symtab/SymbolTableForUnitTesting.scala b/test/junit/scala/tools/nsc/symtab/SymbolTableForUnitTesting.scala index f0f20acf07..8cc7aefdd3 100644 --- a/test/junit/scala/tools/nsc/symtab/SymbolTableForUnitTesting.scala +++ b/test/junit/scala/tools/nsc/symtab/SymbolTableForUnitTesting.scala @@ -2,10 +2,7 @@ package scala.tools.nsc package symtab import scala.reflect.ClassTag -import scala.reflect.internal.{Phase, NoPhase, SomePhase} -import scala.tools.nsc.classpath.FlatClassPath -import scala.tools.nsc.settings.ClassPathRepresentationType -import scala.tools.util.FlatClassPathResolver +import scala.reflect.internal.{NoPhase, Phase, SomePhase} import scala.tools.util.PathResolver import util.ClassPath import io.AbstractFile @@ -30,8 +27,7 @@ class SymbolTableForUnitTesting extends SymbolTable { override def isCompilerUniverse: Boolean = true - def classPath = platform.classPath - def flatClassPath: FlatClassPath = platform.flatClassPath + def classPath: ClassPath = platform.classPath object platform extends backend.Platform { val symbolTable: SymbolTableForUnitTesting.this.type = SymbolTableForUnitTesting.this @@ -39,22 +35,12 @@ class SymbolTableForUnitTesting extends SymbolTable { def platformPhases: List[SubComponent] = Nil - lazy val classPath: ClassPath[AbstractFile] = { - assert(settings.YclasspathImpl.value == ClassPathRepresentationType.Recursive, - "It's not possible to use the recursive classpath representation, when it's not the chosen classpath scanning method") - new PathResolver(settings).result - } - - private[nsc] lazy val flatClassPath: FlatClassPath = { - assert(settings.YclasspathImpl.value == ClassPathRepresentationType.Flat, - "It's not possible to use the flat classpath representation, when it's not the chosen classpath scanning method") - new FlatClassPathResolver(settings).result - } + private[nsc] lazy val classPath: ClassPath = new PathResolver(settings).result def isMaybeBoxed(sym: Symbol): Boolean = ??? def needCompile(bin: AbstractFile, src: AbstractFile): Boolean = ??? def externalEquals: Symbol = ??? - def updateClassPath(subst: Map[ClassPath[AbstractFile], ClassPath[AbstractFile]]): Unit = ??? + def updateClassPath(subst: Map[ClassPath, ClassPath]): Unit = ??? } object loaders extends symtab.SymbolLoaders { @@ -69,10 +55,7 @@ class SymbolTableForUnitTesting extends SymbolTable { class GlobalMirror extends Roots(NoSymbol) { val universe: SymbolTableForUnitTesting.this.type = SymbolTableForUnitTesting.this - def rootLoader: LazyType = settings.YclasspathImpl.value match { - case ClassPathRepresentationType.Flat => new loaders.PackageLoaderUsingFlatClassPath(FlatClassPath.RootPackage, flatClassPath) - case ClassPathRepresentationType.Recursive => new loaders.PackageLoader(classPath) - } + def rootLoader: LazyType = new loaders.PackageLoader(ClassPath.RootPackage, classPath) override def toString = "compiler mirror" } @@ -119,7 +102,9 @@ class SymbolTableForUnitTesting extends SymbolTable { } phasesArray } - lazy val treeInfo: scala.reflect.internal.TreeInfo{val global: SymbolTableForUnitTesting.this.type} = ??? + lazy val treeInfo = new scala.reflect.internal.TreeInfo { + val global: SymbolTableForUnitTesting.this.type = SymbolTableForUnitTesting.this + } val currentFreshNameCreator = new reflect.internal.util.FreshNameCreator diff --git a/test/junit/scala/tools/nsc/transform/delambdafy/DelambdafyTest.scala b/test/junit/scala/tools/nsc/transform/delambdafy/DelambdafyTest.scala index 010078e28a..e4bf038f32 100644 --- a/test/junit/scala/tools/nsc/transform/delambdafy/DelambdafyTest.scala +++ b/test/junit/scala/tools/nsc/transform/delambdafy/DelambdafyTest.scala @@ -52,7 +52,7 @@ object Delambdafy { val srcFile = makeSourceFile(codeForMultiOutput, "delambdafyTest.scala") val outDir = AbstractFile.getDirectory(TempDir.createTempDir()) val outDirPath = outDir.canonicalPath - val extraArgs = "-Ybackend:GenBCode -Ydelambdafy:method" + val extraArgs = "-Ydelambdafy:method" val argsWithOutDir = extraArgs + s" -d $outDirPath -cp $outDirPath" val compiler = newCompilerWithoutVirtualOutdir(extraArgs = argsWithOutDir) compiler.settings.outputDirs.add(srcFile.file, outDir) @@ -63,7 +63,7 @@ object Delambdafy { outDir.delete() classfiles } - + @Test def shouldFindOutputFoldersForAllPromotedLambdasAsMethod(): Unit = { val actual = compileToMultipleOutputWithDelamdbafyMethod() diff --git a/test/junit/scala/tools/nsc/transform/patmat/PatmatBytecodeTest.scala b/test/junit/scala/tools/nsc/transform/patmat/PatmatBytecodeTest.scala new file mode 100644 index 0000000000..ac558e2e21 --- /dev/null +++ b/test/junit/scala/tools/nsc/transform/patmat/PatmatBytecodeTest.scala @@ -0,0 +1,195 @@ +package scala.tools.nsc +package transform.patmat + +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.nsc.backend.jvm.AsmUtils._ +import scala.tools.nsc.backend.jvm.CodeGenTools +import scala.tools.testing.AssertUtil._ + +import CodeGenTools._ +import scala.tools.partest.ASMConverters +import ASMConverters._ +import scala.tools.testing.ClearAfterClass + +object PatmatBytecodeTest extends ClearAfterClass.Clearable { + var compiler = newCompiler() + var optCompiler = newCompiler(extraArgs = "-Yopt:l:project") + def clear(): Unit = { compiler = null; optCompiler = null } +} + +@RunWith(classOf[JUnit4]) +class PatmatBytecodeTest extends ClearAfterClass { + ClearAfterClass.stateToClear = PatmatBytecodeTest + + val compiler = PatmatBytecodeTest.compiler + val optCompiler = PatmatBytecodeTest.optCompiler + + @Test + def t6956(): Unit = { + val code = + """class C { + | private[this] final val ONE = 1 + | + | def s1(i: Byte): Int = i match { + | case ONE => 1 + | case 2 => 2 + | case 3 => 3 + | case _ => 0 + | } + | + | def s2(i: Byte): Int = i match { + | case 1 => 1 + | case 2 => 2 + | case 3 => 3 + | case _ => 0 + | } + |} + """.stripMargin + + val List(c) = compileClasses(compiler)(code) + assert(getSingleMethod(c, "s1").instructions.count(_.opcode == TABLESWITCH) == 1, textify(c)) + assert(getSingleMethod(c, "s2").instructions.count(_.opcode == TABLESWITCH) == 1, textify(c)) + } + + @Test + def t6955(): Unit = { + val code = + """class C { + | type Tag = Byte + | + | def s1(i: Tag): Int = i match { // notice type of i is Tag = Byte + | case 1 => 1 + | case 2 => 2 + | case 3 => 3 + | case _ => 0 + | } + | + | // this worked before, should keep working + | def s2(i: Byte): Int = i match { + | case 1 => 1 + | case 2 => 2 + | case 3 => 3 + | case _ => 0 + | } + |} + """.stripMargin + + val List(c) = compileClasses(compiler)(code) + assert(getSingleMethod(c, "s1").instructions.count(_.opcode == TABLESWITCH) == 1, textify(c)) + assert(getSingleMethod(c, "s2").instructions.count(_.opcode == TABLESWITCH) == 1, textify(c)) + } + + @Test + def optNoPrimitiveTypetest(): Unit = { + val code = + """case class Foo(x: Int, y: String) + |class C { + | def a = Foo(1, "a") match { + | case Foo(_: Int, y) => y + | } + |} + """.stripMargin + val c = compileClasses(optCompiler)(code).head + + assertSameSummary(getSingleMethod(c, "a"), List( + NEW, DUP, ICONST_1, LDC, "<init>", + "y", ARETURN)) + } + + @Test + def optNoNullCheck(): Unit = { + val code = + """case class Foo(x: Any) + |class C { + | def a = (Foo(1): Any) match { + | case Foo(_: String) => + | } + |} + """.stripMargin + val c = compileClasses(optCompiler)(code).head + assert(!getSingleMethod(c, "a").instructions.exists(i => i.opcode == IFNULL || i.opcode == IFNONNULL), textify(findAsmMethod(c, "a"))) + } + + @Test + def optNoLoacalForUnderscore(): Unit = { + val code = + """case class Foo(x: Any, y: String) + |class C { + | def a = (Foo(1, "a"): @unchecked) match { + | case Foo(_: String, y) => y + | } + |} + """.stripMargin + val c = compileClasses(optCompiler)(code).head + assertSameSummary(getSingleMethod(c, "a"), List( + NEW, DUP, ICONST_1, "boxToInteger", LDC, "<init>", ASTORE /*1*/, + ALOAD /*1*/, "y", ASTORE /*2*/, + ALOAD /*1*/, "x", INSTANCEOF, IFNE /*R*/, + NEW, DUP, ALOAD /*1*/, "<init>", ATHROW, + /*R*/ -1, ALOAD /*2*/, ARETURN)) + } + + @Test + def t6941(): Unit = { + val code = + """class C { + | def a(xs: List[Int]) = xs match { + | case x :: _ => x + | } + | def b(xs: List[Int]) = xs match { + | case xs: ::[Int] => xs.head + | } + |} + """.stripMargin + val c = compileClasses(optCompiler)(code, allowMessage = _.msg.contains("may not be exhaustive")).head + + val expected = List( + ALOAD /*1*/ , INSTANCEOF /*::*/ , IFEQ /*A*/ , + ALOAD, CHECKCAST /*::*/ , "head", "unboxToInt", + ISTORE, GOTO /*B*/ , + -1 /*A*/ , NEW /*MatchError*/ , DUP, ALOAD /*1*/ , "<init>", ATHROW, + -1 /*B*/ , ILOAD, IRETURN) + + assertSameSummary(getSingleMethod(c, "a"), expected) + assertSameSummary(getSingleMethod(c, "b"), expected) + } + + @Test + def valPatterns(): Unit = { + val code = + """case class C(a: Any, b: Int) { + | def tplCall = ("hi", 3) + | @inline final def tplInline = (true, 'z') + | + | def t1 = { val (a, b) = (1, 2); a + b } + | def t2 = { val (a, _) = (1, 3); a } + | def t3 = { val (s, i) = tplCall; s.length + i } + | def t4 = { val (_, i) = tplCall; i } + | def t5 = { val (b, c) = tplInline; b || c == 'e' } + | def t6 = { val (_, c) = tplInline; c } + | + | def t7 = { val C(s: String, b) = this; s.length + b } + | def t8 = { val C(_, b) = this; b } + | def t9 = { val C(a, _) = C("hi", 23); a.toString } + |} + """.stripMargin + val List(c, cMod) = compileClasses(optCompiler)(code) + assertSameSummary(getSingleMethod(c, "t1"), List(ICONST_1, ICONST_2, IADD, IRETURN)) + assertSameSummary(getSingleMethod(c, "t2"), List(ICONST_1, IRETURN)) + assertInvokedMethods(getSingleMethod(c, "t3"), List("C.tplCall", "scala/Tuple2._1", "scala/Tuple2._2$mcI$sp", "scala/MatchError.<init>", "java/lang/String.length")) + assertInvokedMethods(getSingleMethod(c, "t4"), List("C.tplCall", "scala/Tuple2._2$mcI$sp", "scala/MatchError.<init>")) + assertNoInvoke(getSingleMethod(c, "t5")) + assertSameSummary(getSingleMethod(c, "t6"), List(BIPUSH, IRETURN)) + + // MatchError reachable because of the type pattern `s: String` + assertInvokedMethods(getSingleMethod(c, "t7"), List("C.a", "C.b", "scala/MatchError.<init>", "java/lang/String.length")) + assertSameSummary(getSingleMethod(c, "t8"), List(ALOAD, "b", IRETURN)) + // C allocation not eliminated - constructor may have side-effects. + assertSameSummary(getSingleMethod(c, "t9"), List(NEW, DUP, LDC, BIPUSH, "<init>", "a", "toString", ARETURN)) + } +} diff --git a/test/junit/scala/tools/nsc/util/ClassPathImplComparator.scala b/test/junit/scala/tools/nsc/util/ClassPathImplComparator.scala deleted file mode 100644 index f2926e3e17..0000000000 --- a/test/junit/scala/tools/nsc/util/ClassPathImplComparator.scala +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright (c) 2014 Contributor. All rights reserved. - */ -package scala.tools.nsc.util - -import scala.reflect.io.AbstractFile -import scala.tools.nsc.Settings -import scala.tools.nsc.settings.ClassPathRepresentationType -import scala.tools.util.PathResolverFactory - -/** - * Simple application to compare efficiency of the recursive and the flat classpath representations - */ -object ClassPathImplComparator { - - private class TestSettings extends Settings { - val checkClasses = PathSetting("-checkClasses", "Specify names of classes which should be found separated with ;", "") - val requiredIterations = IntSetting("-requiredIterations", - "Repeat tests specified number of times (to check e.g. impact of caches)", 1, Some((1, Int.MaxValue)), (_: String) => None) - val cpCreationRepetitions = IntSetting("-cpCreationRepetitions", - "Repeat tests specified number of times (to check e.g. impact of caches)", 1, Some((1, Int.MaxValue)), (_: String) => None) - val cpLookupRepetitions = IntSetting("-cpLookupRepetitions", - "Repeat tests specified number of times (to check e.g. impact of caches)", 1, Some((1, Int.MaxValue)), (_: String) => None) - } - - private class DurationStats(name: String) { - private var sum = 0L - private var iterations = 0 - - def noteMeasuredTime(millis: Long): Unit = { - sum += millis - iterations += 1 - } - - def printResults(): Unit = { - val avg = if (iterations == 0) 0 else sum.toDouble / iterations - println(s"$name - total duration: $sum ms; iterations: $iterations; avg: $avg ms") - } - } - - private lazy val defaultClassesToFind = List( - "scala.collection.immutable.List", - "scala.Option", - "scala.Int", - "scala.collection.immutable.Vector", - "scala.util.hashing.MurmurHash3" - ) - - private val oldCpCreationStats = new DurationStats("Old classpath - create") - private val oldCpSearchingStats = new DurationStats("Old classpath - search") - - private val flatCpCreationStats = new DurationStats("Flat classpath - create") - private val flatCpSearchingStats = new DurationStats("Flat classpath - search") - - def main(args: Array[String]): Unit = { - - if (args contains "-help") - usage() - else { - val oldCpSettings = loadSettings(args.toList, ClassPathRepresentationType.Recursive) - val flatCpSettings = loadSettings(args.toList, ClassPathRepresentationType.Flat) - - val classesToCheck = oldCpSettings.checkClasses.value - val classesToFind = - if (classesToCheck.isEmpty) defaultClassesToFind - else classesToCheck.split(";").toList - - def doTest(classPath: => ClassFileLookup[AbstractFile], cpCreationStats: DurationStats, cpSearchingStats: DurationStats, - cpCreationRepetitions: Int, cpLookupRepetitions: Int)= { - - def createClassPaths() = (1 to cpCreationRepetitions).map(_ => classPath).last - def testClassLookup(cp: ClassFileLookup[AbstractFile]): Boolean = (1 to cpCreationRepetitions).foldLeft(true) { - case (a, _) => a && checkExistenceOfClasses(classesToFind)(cp) - } - - val cp = withMeasuredTime("Creating classpath", createClassPaths(), cpCreationStats) - val result = withMeasuredTime("Searching for specified classes", testClassLookup(cp), cpSearchingStats) - println(s"The end of the test case. All expected classes found = $result \n") - } - - (1 to oldCpSettings.requiredIterations.value) foreach { iteration => - if (oldCpSettings.requiredIterations.value > 1) - println(s"Iteration no $iteration") - - println("Recursive (old) classpath representation:") - doTest(PathResolverFactory.create(oldCpSettings).result, oldCpCreationStats, oldCpSearchingStats, - oldCpSettings.cpCreationRepetitions.value, oldCpSettings.cpLookupRepetitions.value) - - println("Flat classpath representation:") - doTest(PathResolverFactory.create(flatCpSettings).result, flatCpCreationStats, flatCpSearchingStats, - flatCpSettings.cpCreationRepetitions.value, flatCpSettings.cpLookupRepetitions.value) - } - - if (oldCpSettings.requiredIterations.value > 1) { - println("\nOld classpath - summary") - oldCpCreationStats.printResults() - oldCpSearchingStats.printResults() - - println("\nFlat classpath - summary") - flatCpCreationStats.printResults() - flatCpSearchingStats.printResults() - } - } - } - - /** - * Prints usage information - */ - private def usage(): Unit = - println("""Use classpath and sourcepath options like in the case of e.g. 'scala' command. - | There are also two additional options: - | -checkClasses <semicolon separated class names> Specify names of classes which should be found - | -requiredIterations <int value> Repeat tests specified count of times (to check e.g. impact of caches) - | Note: Option -YclasspathImpl will be set automatically for each case. - """.stripMargin.trim) - - private def loadSettings(args: List[String], implType: String) = { - val settings = new TestSettings() - settings.processArguments(args, processAll = true) - settings.YclasspathImpl.value = implType - if (settings.classpath.isDefault) - settings.classpath.value = sys.props("java.class.path") - settings - } - - private def withMeasuredTime[T](operationName: String, f: => T, durationStats: DurationStats): T = { - val startTime = System.currentTimeMillis() - val res = f - val elapsed = System.currentTimeMillis() - startTime - durationStats.noteMeasuredTime(elapsed) - println(s"$operationName - elapsed $elapsed ms") - res - } - - private def checkExistenceOfClasses(classesToCheck: Seq[String])(classPath: ClassFileLookup[AbstractFile]): Boolean = - classesToCheck.foldLeft(true) { - case (res, classToCheck) => - val found = classPath.findClass(classToCheck).isDefined - if (!found) - println(s"Class $classToCheck not found") // of course in this case the measured time will be affected by IO operation - found - } -} diff --git a/test/junit/scala/util/SystemPropertiesTest.scala b/test/junit/scala/util/SystemPropertiesTest.scala new file mode 100644 index 0000000000..38e830eb88 --- /dev/null +++ b/test/junit/scala/util/SystemPropertiesTest.scala @@ -0,0 +1,27 @@ +package scala.util + +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.junit.Test +import org.junit.Assert._ + +@RunWith(classOf[JUnit4]) +class SystemPropertiesTest { + @Test + def filterAll(): Unit = { + val isEmpty = sys.props.filter(_ => false).size == 0 + assertTrue("A filter matching nothing should produce an empty result", isEmpty) + } + + @Test + def filterNone(): Unit = { + val isUnchanged = sys.props.filter(_ => true) == sys.props + assertTrue("A filter matching everything should not change the result", isUnchanged) + } + + @Test + def empty(): Unit = { + val hasSize0 = sys.props.empty.size == 0 + assertTrue("SystemProperties.empty should have size of 0", hasSize0) + } +} diff --git a/test/junit/scala/util/control/ExceptionTest.scala b/test/junit/scala/util/control/ExceptionTest.scala new file mode 100644 index 0000000000..5211d31839 --- /dev/null +++ b/test/junit/scala/util/control/ExceptionTest.scala @@ -0,0 +1,42 @@ +/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2016-2016, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + +package scala.util + +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.junit.Test +import org.junit.Assert._ + +import scala.collection.mutable.ListBuffer + +import scala.util.control.Exception._ + +@RunWith(classOf[JUnit4]) +class ExceptionTest { + + @Test + def andFinally(): Unit = { + + locally { + val audit = ListBuffer[Int]() + val katch = nonFatalCatch[Unit].andFinally(audit append 1) + val result = katch(10) + assertEquals(result, 10) + assertEquals(audit.toList, 1 :: Nil) + } + + locally { + val audit = ListBuffer[Int]() + val katch = nonFatalCatch[Unit].andFinally(audit append 1).andFinally(audit append 2) + val result = katch(20) + assertEquals(result, 20) + assertEquals(audit.toList, 1 :: 2 :: Nil) + } + } +}
\ No newline at end of file |