From 2793b75971ff9c7e874a5c57c6b6c8be96e6d15b Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 7 Feb 2019 16:53:13 +0100 Subject: [PATCH 1/2] Fix #3340: Always throw when casting to scala.Nothing Also fix #4410 --- .../dotty/tools/dotc/core/Definitions.scala | 4 ++ .../tools/dotc/transform/TypeTestsCasts.scala | 7 +++ tests/pos/i828.scala | 3 ++ tests/run/i3340.check | 5 +++ tests/run/i3340.scala | 45 +++++++++++++++++++ tests/run/i4410.scala | 7 +++ 6 files changed, 71 insertions(+) create mode 100644 tests/pos/i828.scala create mode 100644 tests/run/i3340.check create mode 100644 tests/run/i3340.scala create mode 100644 tests/run/i4410.scala diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 2332a9b8204e..79d0e4afedc7 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -570,6 +570,10 @@ class Definitions { lazy val ClassClass: ClassSymbol = ctx.requiredClass("java.lang.Class") lazy val BoxedNumberClass: ClassSymbol = ctx.requiredClass("java.lang.Number") lazy val ClassCastExceptionClass: ClassSymbol = ctx.requiredClass("java.lang.ClassCastException") + lazy val ClassCastExceptionClass_stringConstructor: TermSymbol = ClassCastExceptionClass.info.member(nme.CONSTRUCTOR).suchThat(_.info.firstParamTypes match { + case List(pt) => (pt isRef StringClass) + case _ => false + }).symbol.asTerm lazy val ArithmeticExceptionClass: ClassSymbol = ctx.requiredClass("java.lang.ArithmeticException") lazy val ArithmeticExceptionClass_stringConstructor: TermSymbol = ArithmeticExceptionClass.info.member(nme.CONSTRUCTOR).suchThat(_.info.firstParamTypes match { case List(pt) => (pt isRef StringClass) diff --git a/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala b/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala index b0bac608048e..d4c1ac78cbde 100644 --- a/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala +++ b/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala @@ -244,6 +244,13 @@ object TypeTestsCasts { else if (isDerivedValueClass(testCls)) { expr // adaptToType in Erasure will do the necessary type adaptation } + else if (testCls eq defn.NothingClass) { + // In the JVM `x.asInstanceOf[Nothing]` would throw a class cast exception except when `x eq null`. + // To avoid this loophole we execute `x` and then regardless of the result throw a `ClassCastException` + val throwCCE = Throw(New(defn.ClassCastExceptionClass.typeRef, defn.ClassCastExceptionClass_stringConstructor, + Literal(Constant("Cannot cast to scala.Nothing")) :: Nil)) + Block(expr :: Nil, throwCCE).withSpan(expr.span) + } else derivedTree(expr, defn.Any_asInstanceOf, testType) } diff --git a/tests/pos/i828.scala b/tests/pos/i828.scala new file mode 100644 index 000000000000..77dc0d4901c3 --- /dev/null +++ b/tests/pos/i828.scala @@ -0,0 +1,3 @@ +object X { + val x: Int = null.asInstanceOf[Nothing] +} diff --git a/tests/run/i3340.check b/tests/run/i3340.check new file mode 100644 index 000000000000..2a1709530e46 --- /dev/null +++ b/tests/run/i3340.check @@ -0,0 +1,5 @@ +Test$.f1(i3340.scala:12) +Test$.f2(i3340.scala:16) +Test$.f3(i3340.scala:20) +Test$.f4(i3340.scala:27) +Test$.f5(i3340.scala:34) diff --git a/tests/run/i3340.scala b/tests/run/i3340.scala new file mode 100644 index 000000000000..14bfda6527d2 --- /dev/null +++ b/tests/run/i3340.scala @@ -0,0 +1,45 @@ +object Test { + def main(args: Array[String]): Unit = { + printlnStackLine(f1) + printlnStackLine(f2) + printlnStackLine(f3) + printlnStackLine(f4) + printlnStackLine(f5) + } + + def f1: Unit = { + val a: Nothing = + null.asInstanceOf[Nothing] // throws here + } + + def f2: Unit = { + null.asInstanceOf[Nothing] // throws here + } + + def f3: Unit = { + null.asInstanceOf[Nothing] // throws here + () + } + + + def f4: Unit = { + val n: Any = null + n.asInstanceOf[Nothing] // throws here + () + } + + def f5: Unit = { + val n: Any = null + val a: Nothing = + n.asInstanceOf[Nothing] // throws here + () + } + + def printlnStackLine(t: => Any): Unit = { + try t + catch { + case e: ClassCastException => + println(e.getStackTrace.head) + } + } +} diff --git a/tests/run/i4410.scala b/tests/run/i4410.scala new file mode 100644 index 000000000000..a123b60c7c87 --- /dev/null +++ b/tests/run/i4410.scala @@ -0,0 +1,7 @@ +object Test { + val a = + try null.asInstanceOf[Nothing] + catch { case e: ClassCastException if e.getMessage == "Cannot cast to scala.Nothing" => /* As expected */ } + def main(args: Array[String]): Unit = { + } +} From 2b7439406678428b04106190e0d883d0f46b934e Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Fri, 8 Feb 2019 11:22:04 +0100 Subject: [PATCH 2/2] Add test for #3340 with a side effect --- tests/run/i3340.check | 12 +++++++----- tests/run/i3340.scala | 8 ++++++++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/tests/run/i3340.check b/tests/run/i3340.check index 2a1709530e46..bcb629b9186a 100644 --- a/tests/run/i3340.check +++ b/tests/run/i3340.check @@ -1,5 +1,7 @@ -Test$.f1(i3340.scala:12) -Test$.f2(i3340.scala:16) -Test$.f3(i3340.scala:20) -Test$.f4(i3340.scala:27) -Test$.f5(i3340.scala:34) +Test$.f1(i3340.scala:13) +Test$.f2(i3340.scala:17) +Test$.f3(i3340.scala:21) +Test$.f4(i3340.scala:28) +Test$.f5(i3340.scala:35) +foo +Test$.f6(i3340.scala:42) diff --git a/tests/run/i3340.scala b/tests/run/i3340.scala index 14bfda6527d2..328305cf03ae 100644 --- a/tests/run/i3340.scala +++ b/tests/run/i3340.scala @@ -5,6 +5,7 @@ object Test { printlnStackLine(f3) printlnStackLine(f4) printlnStackLine(f5) + printlnStackLine(f6) } def f1: Unit = { @@ -35,6 +36,13 @@ object Test { () } + def f6: Unit = { + val n: Any = null + val a: Nothing = + { println("foo"); n }.asInstanceOf[Nothing] // throws here + () + } + def printlnStackLine(t: => Any): Unit = { try t catch {