summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJames Iry <jamesiry@gmail.com>2013-02-25 16:25:22 -0800
committerJames Iry <jamesiry@gmail.com>2013-02-25 16:26:53 -0800
commit28a716190c5faf549ed302a1c19d9611c32d2010 (patch)
tree5f4b0472cb3e9621a9823cbd463b6c2ad4992f76
parentf2be783020a1c8e05ebcae3717740632b41d1751 (diff)
downloadscala-28a716190c5faf549ed302a1c19d9611c32d2010.tar.gz
scala-28a716190c5faf549ed302a1c19d9611c32d2010.tar.bz2
scala-28a716190c5faf549ed302a1c19d9611c32d2010.zip
SI-7181 Prepare to remove duplicated finally blocks
As a first step towards fixing 7181, this commit improves the comments and variable names around generating try/catch/finally blocks in GenICode and adds a test verifying the current functionality of try/catch/finally blocks
-rw-r--r--src/compiler/scala/tools/nsc/backend/icode/GenICode.scala77
-rw-r--r--test/files/run/t7181.check23
-rw-r--r--test/files/run/t7181.scala78
3 files changed, 157 insertions, 21 deletions
diff --git a/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala b/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala
index 122972039b..8881650a81 100644
--- a/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala
+++ b/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala
@@ -1921,15 +1921,47 @@ abstract class GenICode extends SubComponent {
* }), (AnotherExceptionClass,
* ctx => {...
* } ))`
+ *
+ * The resulting structure will look something like
+ *
+ * outer:
+ * // this 'useless' jump will be removed later,
+ * // for now it separates the try body's blocks from previous
+ * // code since the try body needs its own exception handlers
+ * JUMP body
+ *
+ * body:
+ * [ try body ]
+ * [ finally body ]
+ * JUMP normalExit
+ *
+ * catch[i]:
+ * [ handler[i] body ]
+ * [ finally body ]
+ * JUMP normalExit
+ *
+ * catchAll:
+ * STORE exception
+ * [ finally body ]
+ * THROW exception
+ *
+ * normalExit:
+ *
+ * each catch[i] will cover body. catchAll will cover both body and each catch[i]
+ * Additional finally copies are created on the emission of every RETURN in the try body and exception handlers.
+ *
+ * This could result in unreachable code which has to be cleaned up later, e.g. if the try and all the exception
+ * handlers always end in RETURN then there will be no "normal" flow out of the try/catch/finally.
+ * Later reachability analysis will remove unreacahble code.
*/
def Try(body: Context => Context,
handlers: List[(Symbol, TypeKind, Context => Context)],
finalizer: Tree,
tree: Tree) = {
- val outerCtx = this.dup // context for generating exception handlers, covered by finalizer
+ val outerCtx = this.dup // context for generating exception handlers, covered by the catch-all finalizer
val finalizerCtx = this.dup // context for generating finalizer handler
- val afterCtx = outerCtx.newBlock()
+ val normalExitCtx = outerCtx.newBlock() // context where flow will go on a "normal" (non-return, non-throw) exit from a try or catch handler
var tmp: Local = null
val kind = toTypeKind(tree.tpe)
val guardResult = kind != UNIT && mayCleanStack(finalizer)
@@ -1956,30 +1988,33 @@ abstract class GenICode extends SubComponent {
} else ctx
+ // Generate the catch-all exception handler that deals with uncaught exceptions coming
+ // from the try or exception handlers. It catches the exception, runs the finally code, then rethrows
+ // the exception
if (finalizer != EmptyTree) {
val exh = outerCtx.newExceptionHandler(NoSymbol, finalizer.pos) // finalizer covers exception handlers
this.addActiveHandler(exh) // .. and body aswell
- val ctx = finalizerCtx.enterExceptionHandler(exh)
- val exception = ctx.makeLocal(finalizer.pos, ThrowableClass.tpe, "exc")
- loadException(ctx, exh, finalizer.pos)
- ctx.bb.emit(STORE_LOCAL(exception))
- val ctx1 = genLoad(finalizer, ctx, UNIT)
- ctx1.bb.emit(LOAD_LOCAL(exception))
- ctx1.bb.emit(THROW(ThrowableClass))
- ctx1.bb.enterIgnoreMode()
- ctx1.bb.close()
+ val exhStartCtx = finalizerCtx.enterExceptionHandler(exh)
+ val exception = exhStartCtx.makeLocal(finalizer.pos, ThrowableClass.tpe, "exc")
+ loadException(exhStartCtx, exh, finalizer.pos)
+ exhStartCtx.bb.emit(STORE_LOCAL(exception))
+ val exhEndCtx = genLoad(finalizer, exhStartCtx, UNIT)
+ exhEndCtx.bb.emit(LOAD_LOCAL(exception))
+ exhEndCtx.bb.closeWith(THROW(ThrowableClass))
+ exhEndCtx.bb.enterIgnoreMode()
finalizerCtx.endHandler()
}
+ // Generate each exception handler
for ((sym, kind, handler) <- handlers) {
val exh = this.newExceptionHandler(sym, tree.pos)
- var ctx1 = outerCtx.enterExceptionHandler(exh)
- ctx1.addFinalizer(finalizer, finalizerCtx)
- loadException(ctx1, exh, tree.pos)
- ctx1 = handler(ctx1)
+ val exhStartCtx = outerCtx.enterExceptionHandler(exh)
+ exhStartCtx.addFinalizer(finalizer, finalizerCtx)
+ loadException(exhStartCtx, exh, tree.pos)
+ val exhEndCtx = handler(exhStartCtx)
// emit finalizer
- val ctx2 = emitFinalizer(ctx1)
- ctx2.bb.closeWith(JUMP(afterCtx.bb))
+ val exhEndCtx2 = emitFinalizer(exhEndCtx)
+ exhEndCtx2.bb.closeWith(JUMP(normalExitCtx.bb))
outerCtx.endHandler()
}
@@ -1987,14 +2022,14 @@ abstract class GenICode extends SubComponent {
if (finalizer != EmptyTree)
bodyCtx.addFinalizer(finalizer, finalizerCtx)
- var finalCtx = body(bodyCtx)
- finalCtx = emitFinalizer(finalCtx)
+ var bodyEndCtx = body(bodyCtx)
+ bodyEndCtx = emitFinalizer(bodyEndCtx)
outerCtx.bb.closeWith(JUMP(bodyCtx.bb))
- finalCtx.bb.closeWith(JUMP(afterCtx.bb))
+ bodyEndCtx.bb.closeWith(JUMP(normalExitCtx.bb))
- afterCtx
+ normalExitCtx
}
}
}
diff --git a/test/files/run/t7181.check b/test/files/run/t7181.check
new file mode 100644
index 0000000000..e4b8e30dfe
--- /dev/null
+++ b/test/files/run/t7181.check
@@ -0,0 +1,23 @@
+normal exit MainNormalExit
+finally MainNormalExit
+normal flow MainNormalExit
+
+return MainReturn
+finally MainReturn
+
+uncaught exception MainUncaughtException
+finally MainUncaughtException
+
+caught exception ExceptionNormalExit
+normal exit ExceptionNormalExit
+finally ExceptionNormalExit
+normal flow ExceptionNormalExit
+
+caught exception ExceptionReturn
+return ExceptionReturn
+finally ExceptionReturn
+
+caught exception ExceptionUncaughtException
+uncaught exception ExceptionUncaughtException
+finally ExceptionUncaughtException
+
diff --git a/test/files/run/t7181.scala b/test/files/run/t7181.scala
new file mode 100644
index 0000000000..a055e43481
--- /dev/null
+++ b/test/files/run/t7181.scala
@@ -0,0 +1,78 @@
+sealed abstract class Action
+// exit the try body normally
+case object MainNormalExit extends Action
+// exit the try body with a 'return'
+case object MainReturn extends Action
+// exit the try body with an uncaught exception
+case object MainUncaughtException extends Action
+// exit the try body with a caught exception and exit the exception handler normally
+case object ExceptionNormalExit extends Action
+// exit the try body with a caught exception and exit the exception handler with a 'return'
+case object ExceptionReturn extends Action
+// exit the try body with a caught exception and exit the exception handler with an uncaught exception
+case object ExceptionUncaughtException extends Action
+
+case class UncaughtException(action: Action) extends RuntimeException
+case class CaughtException(action: Action) extends RuntimeException
+
+object Test extends App {
+ def test(action: Action, expectException: Boolean = false) {
+ var gotException = false
+ val result = try
+ driver(action)
+ catch {
+ case UncaughtException(a) =>
+ gotException = true
+ a
+ }
+ if (gotException) assert(expectException, "Got unexpected exception")
+ else assert(!expectException, "Did not get expected exception")
+
+ assert(result == action, s"Expected $action but got $result")
+ println()
+ }
+
+ def driver(action: Action): Action = {
+ val result = try {
+ action match {
+ case MainNormalExit =>
+ println(s"normal exit $action")
+ action
+ case MainReturn =>
+ println(s"return $action")
+ return action
+ case MainUncaughtException =>
+ println(s"uncaught exception $action")
+ throw UncaughtException(action)
+ case _ =>
+ println(s"caught exception $action")
+ throw CaughtException(action)
+ }
+ } catch {
+ case CaughtException(action) => action match {
+ case ExceptionNormalExit =>
+ println(s"normal exit $action")
+ action
+ case ExceptionReturn =>
+ println(s"return $action")
+ return action
+ case ExceptionUncaughtException =>
+ println(s"uncaught exception $action")
+ throw UncaughtException(action)
+ case _ =>
+ sys.error(s"unexpected $action in exception handler")
+ }
+ } finally {
+ println(s"finally $action")
+ }
+ println(s"normal flow $action")
+ result
+ }
+
+ test(MainNormalExit)
+ test(MainReturn)
+ test(MainUncaughtException, true)
+ test(ExceptionNormalExit)
+ test(ExceptionReturn)
+ test(ExceptionUncaughtException, true)
+}