TOREE-341 Fixed exception stack traces getting lost. This is a rather ugly fix to address the issue caused by https://issues.scala-lang.org/browse/SI-8935
The crux of the issue is that valueOfTerm returns None -- always This introduces a small class that is bound into the REPL scope so that we can modify its internal state and use that to retrieve values. We can extend this to a more general case later on if needed. Project: http://git-wip-us.apache.org/repos/asf/incubator-toree/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-toree/commit/7e947b4e Tree: http://git-wip-us.apache.org/repos/asf/incubator-toree/tree/7e947b4e Diff: http://git-wip-us.apache.org/repos/asf/incubator-toree/diff/7e947b4e Branch: refs/heads/master Commit: 7e947b4ea8876cc6050acd5e968760fa4241de6c Parents: 1ea7b56 Author: Marius Van Niekerk <mniek...@vm179.corp.maxpointinteractive.com> Authored: Wed Sep 21 11:17:28 2016 -0400 Committer: Marius van Niekerk <marius.v.niek...@gmail.com> Committed: Tue Sep 27 10:23:47 2016 -0400 ---------------------------------------------------------------------- .../scala/ScalaInterpreterSpecific.scala | 54 +++++++++++++++++--- 1 file changed, 46 insertions(+), 8 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-toree/blob/7e947b4e/scala-interpreter/src/main/scala-2.11/org/apache/toree/kernel/interpreter/scala/ScalaInterpreterSpecific.scala ---------------------------------------------------------------------- diff --git a/scala-interpreter/src/main/scala-2.11/org/apache/toree/kernel/interpreter/scala/ScalaInterpreterSpecific.scala b/scala-interpreter/src/main/scala-2.11/org/apache/toree/kernel/interpreter/scala/ScalaInterpreterSpecific.scala index be729e0..6a31a55 100644 --- a/scala-interpreter/src/main/scala-2.11/org/apache/toree/kernel/interpreter/scala/ScalaInterpreterSpecific.scala +++ b/scala-interpreter/src/main/scala-2.11/org/apache/toree/kernel/interpreter/scala/ScalaInterpreterSpecific.scala @@ -36,6 +36,7 @@ trait ScalaInterpreterSpecific extends SettingsProducerLike { this: ScalaInterpr private var iMain: IMain = _ private var jLineCompleter: JLineCompletion = _ + private val exceptionHack = new ExceptionHack() def _runtimeClassloader = { _thisClassloader @@ -293,6 +294,9 @@ trait ScalaInterpreterSpecific extends SettingsProducerLike { this: ScalaInterpr // ADD IMPORTS generates too many classes, client is responsible for adding import logger.debug("Adding org.apache.spark.SparkContext._ to imports") iMain.interpret("import org.apache.spark.SparkContext._") + + logger.debug("Adding the hack for the exception handling retrieval.") + iMain.bind("_exceptionHack", exceptionHack) } this @@ -347,6 +351,20 @@ trait ScalaInterpreterSpecific extends SettingsProducerLike { this: ScalaInterpr } } + private def retrieveLastException: Throwable = { + iMain.interpret("_exceptionHack.lastException = lastException") + exceptionHack.lastException + } + + private def clearLastException(): Unit = { + iMain.directBind( + ExecutionExceptionName, + classOf[Throwable].getName, + null + ) + exceptionHack.lastException = null + } + protected def interpretMapToResultAndExecuteInfo( future: Future[(Results.Result, String)] ): Future[(Results.Result, Either[ExecuteOutput, ExecuteFailure])] = { @@ -356,11 +374,12 @@ trait ScalaInterpreterSpecific extends SettingsProducerLike { this: ScalaInterpr case (Results.Incomplete, output) => (Results.Incomplete, Left(output)) case (Results.Aborted, output) => (Results.Aborted, Right(null)) case (Results.Error, output) => + val ex = Some(retrieveLastException) ( Results.Error, Right( interpretConstructExecuteError( - read(ExecutionExceptionName), + ex, output ) ) @@ -375,16 +394,24 @@ trait ScalaInterpreterSpecific extends SettingsProducerLike { this: ScalaInterpr // Runtime error case Some(e) if e != null => val ex = e.asInstanceOf[Throwable] - // Clear runtime error message - iMain.directBind( - ExecutionExceptionName, - classOf[Throwable].getName, - null - ) + clearLastException() + + // The scala REPL does a pretty good job of returning us a stack trace that is free from all the bits that the + // interpreter uses before it. + // + // The REPL emits its message as something like this, so trim off the first and last element + // + // java.lang.ArithmeticException: / by zero + // at failure(<console>:17) + // at call_failure(<console>:19) + // ... 40 elided + + val formattedException = output.split("\n") + ExecuteError( ex.getClass.getName, ex.getLocalizedMessage, - ex.getStackTrace.map(_.toString).toList + formattedException.slice(1, formattedException.size - 1).toList ) // Compile time error, need to check internal reporter case _ => @@ -399,3 +426,14 @@ trait ScalaInterpreterSpecific extends SettingsProducerLike { this: ScalaInterpr ExecuteError("Unknown", "Unable to retrieve error!", List()) } } + +/** + * Due to a bug in the scala interpreter under scala 2.11 (SI-8935) with IMain.valueOfTerm we can hack around it by + * binding an instance of ExceptionHack into iMain and interpret the "_exceptionHack.lastException = lastException". + * This makes it possible to extract the exception. + * + * TODO: Revisit this once Scala 2.12 is released. + */ +class ExceptionHack { + var lastException: Throwable = _ +} \ No newline at end of file