Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

SI-7006 Prevent unreachable blocks in GenICode

This commit makes GenICode prevent the generation of
most unreachable blocks. The new unreachable block prevention code can
be disabled with a compiler flag.

Because full unreachable analysis is no longer necessary for
normal code it makes the unreachable block analysis run only under
-optimise.

A test is included to make sure unreachable code doesn't cause issues
in code gen.

A concrete example will help.

  def foo(): X = {
   try
     return something()
   catch {
     case e: Throwable =>
        println(e)
        throw e
   }
   unreachableCode()
  ]

Here unreachableCode() is unreachable but GenICode would create ICode
for it and then ASM would turn it into a pile of NOPS.

A previous commit added a reachability analysis step to eliminate
that unreachable code but that added a bit of time to the
compilation process even when optimization was turned off.
This commit avoids generating most unreachable
ICode in the first place so that full reachability analysis is
only needed after doing other optimization work.

The new code works by extending a mechanism that was already in place.
When GenICode encountered a THROW or RETURN it would put the
current block into "ignore" mode so that no further instructions
would be written into the block. However, that ignore mode flag
was itself ignored when it came to figuring out if follow on blocks
should  be written. So this commit goes through places like try/catch
and if/else and uses the ignore mode of the current block to decide
whether to create follow on blocks, or if it already has, to kill by
putting them into ignore mode and closing them where they'll be
removed from the method's list of active blocks.

It's not quite as good as full reachability analysis. In particular
because a label def can be emitted before anything that jumps to it,
this simple logic is forced to leave label defs alone and that means
some of them may be unreachable without being removed. However, in
practice it gets close the the benefit of reachability analysis at
very nearly no cost.
  • Loading branch information...
commit b50a0d811f0fb99ccbc295741e66bab348b3f99e 1 parent b8c8299
@JamesIry JamesIry authored
View
32 src/compiler/scala/tools/nsc/backend/icode/BasicBlocks.scala
@@ -17,7 +17,7 @@ trait BasicBlocks {
self: ICodes =>
import opcodes._
- import global.{ settings, log, nme }
+ import global.{ settings, debuglog, log, nme }
import nme.isExceptionResultName
/** Override Array creation for efficiency (to not go through reflection). */
@@ -383,7 +383,6 @@ trait BasicBlocks {
/** Close the block */
def close() {
assert(!closed || ignore, this)
- assert(instructionList.nonEmpty, "Empty block: " + this)
if (ignore && closed) { // redundant `ignore &&` for clarity -- we should never be in state `!ignore && closed`
// not doing anything to this block is important...
// because the else branch reverses innocent blocks, which is wrong when they're in ignore mode (and closed)
@@ -393,9 +392,38 @@ trait BasicBlocks {
setFlag(DIRTYSUCCS)
instructionList = instructionList.reverse
instrs = instructionList.toArray
+ if (instructionList.isEmpty) {
+ debuglog(s"Removing empty block $this")
+ code removeBlock this
+ }
}
}
+ /**
+ * if cond is true, closes this block, entersIgnoreMode, and removes the block from
+ * its list of blocks. Used to allow a block to be started and then cancelled when it
+ * is discovered to be unreachable.
+ */
+ def killIf(cond: Boolean) {
+ if (!settings.YdisableUnreachablePrevention.value && cond) {
+ debuglog(s"Killing block $this")
+ assert(instructionList.isEmpty, s"Killing a non empty block $this")
+ // only checked under debug because fetching predecessor list is moderately expensive
+ if (settings.debug.value)
+ assert(predecessors.isEmpty, s"Killing block $this which is referred to from ${predecessors.mkString}")
+
+ close()
+ enterIgnoreMode()
+ }
+ }
+
+ /**
+ * Same as killIf but with the logic of the condition reversed
+ */
+ def killUnless(cond: Boolean) {
+ this killIf !cond
+ }
+
def open() {
assert(closed, this)
closed = false
View
117 src/compiler/scala/tools/nsc/backend/icode/GenICode.scala
@@ -376,12 +376,14 @@ abstract class GenICode extends SubComponent {
"I produce UNIT in a context where " + expectedType + " is expected!")
// alternatives may be already closed by a tail-recursive jump
+ val contReachable = !(thenCtx.bb.ignore && elseCtx.bb.ignore)
thenCtx.bb.closeWith(JUMP(contCtx.bb))
elseCtx.bb.closeWith(
if (elsep == EmptyTree) JUMP(contCtx.bb)
else JUMP(contCtx.bb) setPos tree.pos
)
+ contCtx.bb killUnless contReachable
(contCtx, resKind)
}
private def genLoadTry(tree: Try, ctx: Context, setGeneratedType: TypeKind => Unit): Context = {
@@ -477,7 +479,11 @@ abstract class GenICode extends SubComponent {
val resCtx: Context = tree match {
case LabelDef(name, params, rhs) =>
def genLoadLabelDef = {
- val ctx1 = ctx.newBlock()
+ val ctx1 = ctx.newBlock() // note: we cannot kill ctx1 if ctx is in ignore mode because
+ // label defs can be the target of jumps from other locations.
+ // that means label defs can lead to unreachable code without
+ // proper reachability analysis
+
if (nme.isLoopHeaderLabel(name))
ctx1.bb.loopHeader = true
@@ -560,6 +566,7 @@ abstract class GenICode extends SubComponent {
// the list, otherwise infinite recursion happens for
// finalizers that contain 'return'
val fctx = finalizerCtx.newBlock()
+ fctx.bb killIf ctx1.bb.ignore
ctx1.bb.closeWith(JUMP(fctx.bb))
ctx1 = genLoad(f1, fctx, UNIT)
}
@@ -949,6 +956,8 @@ abstract class GenICode extends SubComponent {
debuglog("Generating SWITCH statement.")
val ctx1 = genLoad(selector, ctx, INT) // TODO: Java 7 allows strings in switches (so, don't assume INT and don't convert the literals using intValue)
val afterCtx = ctx1.newBlock()
+ afterCtx.bb killIf ctx1.bb.ignore
+ var afterCtxReachable = false
var caseCtx: Context = null
generatedType = toTypeKind(tree.tpe)
@@ -959,6 +968,7 @@ abstract class GenICode extends SubComponent {
for (caze @ CaseDef(pat, guard, body) <- cases) {
assert(guard == EmptyTree, guard)
val tmpCtx = ctx1.newBlock()
+ tmpCtx.bb killIf ctx1.bb.ignore
pat match {
case Literal(value) =>
tags = value.intValue :: tags
@@ -980,12 +990,15 @@ abstract class GenICode extends SubComponent {
}
caseCtx = genLoad(body, tmpCtx, generatedType)
+ afterCtxReachable |= !caseCtx.bb.ignore
// close the block unless it's already been closed by the body, which closes the block if it ends in a jump (which is emitted to have alternatives share their body)
caseCtx.bb.closeWith(JUMP(afterCtx.bb) setPos caze.pos)
}
+ afterCtxReachable |= (default == afterCtx)
ctx1.bb.emitOnly(
SWITCH(tags.reverse map (x => List(x)), (default :: targets).reverse) setPos tree.pos
)
+ afterCtx.bb killUnless afterCtxReachable
afterCtx
}
genLoadMatch
@@ -1342,9 +1355,9 @@ abstract class GenICode extends SubComponent {
private def genCond(tree: Tree,
ctx: Context,
thenCtx: Context,
- elseCtx: Context): Unit =
- {
- def genComparisonOp(l: Tree, r: Tree, code: Int) {
+ elseCtx: Context): Boolean =
+ {
+ def genComparisonOp(l: Tree, r: Tree, code: Int): Boolean = {
val op: TestOp = code match {
case scalaPrimitives.LT => LT
case scalaPrimitives.LE => LE
@@ -1360,27 +1373,33 @@ abstract class GenICode extends SubComponent {
lazy val nonNullSide = ifOneIsNull(l, r)
if (isReferenceEqualityOp(code) && nonNullSide != null) {
val ctx1 = genLoad(nonNullSide, ctx, ObjectReference)
+ val branchesReachable = !ctx1.bb.ignore
ctx1.bb.emitOnly(
CZJUMP(thenCtx.bb, elseCtx.bb, op, ObjectReference)
)
+ branchesReachable
}
else {
val kind = getMaxType(l.tpe :: r.tpe :: Nil)
var ctx1 = genLoad(l, ctx, kind)
ctx1 = genLoad(r, ctx1, kind)
+ val branchesReachable = !ctx1.bb.ignore
ctx1.bb.emitOnly(
CJUMP(thenCtx.bb, elseCtx.bb, op, kind) setPos r.pos
)
+ branchesReachable
}
}
debuglog("Entering genCond with tree: " + tree)
// the default emission
- def default() = {
+ def default(): Boolean = {
val ctx1 = genLoad(tree, ctx, BOOL)
+ val branchesReachable = !ctx1.bb.ignore
ctx1.bb.closeWith(CZJUMP(thenCtx.bb, elseCtx.bb, NE, BOOL) setPos tree.pos)
+ branchesReachable
}
tree match {
@@ -1392,11 +1411,12 @@ abstract class GenICode extends SubComponent {
lazy val Select(lhs, _) = fun
lazy val rhs = args.head
- def genZandOrZor(and: Boolean) = {
+ def genZandOrZor(and: Boolean): Boolean = {
val ctxInterm = ctx.newBlock()
- if (and) genCond(lhs, ctx, ctxInterm, elseCtx)
+ val branchesReachable = if (and) genCond(lhs, ctx, ctxInterm, elseCtx)
else genCond(lhs, ctx, thenCtx, ctxInterm)
+ ctxInterm.bb killUnless branchesReachable
genCond(rhs, ctxInterm, thenCtx, elseCtx)
}
@@ -1436,7 +1456,7 @@ abstract class GenICode extends SubComponent {
* @param thenCtx target context if the comparison yields true
* @param elseCtx target context if the comparison yields false
*/
- def genEqEqPrimitive(l: Tree, r: Tree, ctx: Context)(thenCtx: Context, elseCtx: Context): Unit = {
+ def genEqEqPrimitive(l: Tree, r: Tree, ctx: Context)(thenCtx: Context, elseCtx: Context): Boolean = {
def getTempLocal = ctx.method.lookupLocal(nme.EQEQ_LOCAL_VAR) getOrElse {
ctx.makeLocal(l.pos, AnyRefClass.tpe, nme.EQEQ_LOCAL_VAR.toString)
}
@@ -1476,26 +1496,40 @@ abstract class GenICode extends SubComponent {
val ctx1 = genLoad(l, ctx, ObjectReference)
val ctx2 = genLoad(r, ctx1, ObjectReference)
+ val branchesReachable = !ctx2.bb.ignore
ctx2.bb.emitOnly(
CALL_METHOD(equalsMethod, if (settings.optimise.value) Dynamic else Static(onInstance = false)),
CZJUMP(thenCtx.bb, elseCtx.bb, NE, BOOL)
)
+ branchesReachable
}
else {
- if (isNull(l))
+ if (isNull(l)) {
// null == expr -> expr eq null
- genLoad(r, ctx, ObjectReference).bb emitOnly CZJUMP(thenCtx.bb, elseCtx.bb, EQ, ObjectReference)
- else if (isNull(r)) {
+ val ctx1 = genLoad(r, ctx, ObjectReference)
+ val branchesReachable = !ctx1.bb.ignore
+ ctx1.bb emitOnly CZJUMP(thenCtx.bb, elseCtx.bb, EQ, ObjectReference)
+ branchesReachable
+ } else if (isNull(r)) {
// expr == null -> expr eq null
- genLoad(l, ctx, ObjectReference).bb emitOnly CZJUMP(thenCtx.bb, elseCtx.bb, EQ, ObjectReference)
+ val ctx1 = genLoad(l, ctx, ObjectReference)
+ val branchesReachable = !ctx1.bb.ignore
+ ctx1.bb emitOnly CZJUMP(thenCtx.bb, elseCtx.bb, EQ, ObjectReference)
+ branchesReachable
} else {
val eqEqTempLocal = getTempLocal
var ctx1 = genLoad(l, ctx, ObjectReference)
- lazy val nonNullCtx = ctx1.newBlock()
+ val branchesReachable = !ctx1.bb.ignore
+ lazy val nonNullCtx = {
+ val block = ctx1.newBlock()
+ block.bb killUnless branchesReachable
+ block
+ }
// l == r -> if (l eq null) r eq null else l.equals(r)
ctx1 = genLoad(r, ctx1, ObjectReference)
val nullCtx = ctx1.newBlock()
+ nullCtx.bb killUnless branchesReachable
ctx1.bb.emitOnly(
STORE_LOCAL(eqEqTempLocal) setPos l.pos,
@@ -1512,6 +1546,7 @@ abstract class GenICode extends SubComponent {
CALL_METHOD(Object_equals, Dynamic),
CZJUMP(thenCtx.bb, elseCtx.bb, NE, BOOL)
)
+ branchesReachable
}
}
}
@@ -1957,6 +1992,7 @@ abstract class GenICode extends SubComponent {
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 normalExitCtx = outerCtx.newBlock() // context where flow will go on a "normal" (non-return, non-throw) exit from a try or catch handler
+ var normalExitReachable = false
var tmp: Local = null
val kind = toTypeKind(tree.tpe)
val guardResult = kind != UNIT && mayCleanStack(finalizer)
@@ -1971,6 +2007,7 @@ abstract class GenICode extends SubComponent {
def emitFinalizer(ctx: Context): Context = if (!finalizer.isEmpty) {
val ctx1 = finalizerCtx.dup.newBlock()
+ ctx1.bb killIf ctx.bb.ignore
ctx.bb.closeWith(JUMP(ctx1.bb))
if (guardResult) {
@@ -1986,32 +2023,38 @@ abstract class GenICode extends SubComponent {
// 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 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)
- val exhStartCtx = outerCtx.enterExceptionHandler(exh)
- exhStartCtx.addFinalizer(finalizer, finalizerCtx)
- loadException(exhStartCtx, exh, tree.pos)
- val exhEndCtx = handler(exhStartCtx)
- exhEndCtx.bb.closeWith(JUMP(normalExitCtx.bb))
- outerCtx.endHandler()
+ if (settings.YdisableUnreachablePrevention.value || !outerCtx.bb.ignore) {
+ if (finalizer != EmptyTree) {
+ val exh = outerCtx.newExceptionHandler(NoSymbol, finalizer.pos) // finalizer covers exception handlers
+ this.addActiveHandler(exh) // .. and body aswell
+ val exhStartCtx = finalizerCtx.enterExceptionHandler(exh)
+ exhStartCtx.bb killIf outerCtx.bb.ignore
+ 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)
+ val exhStartCtx = outerCtx.enterExceptionHandler(exh)
+ exhStartCtx.bb killIf outerCtx.bb.ignore
+ exhStartCtx.addFinalizer(finalizer, finalizerCtx)
+ loadException(exhStartCtx, exh, tree.pos)
+ val exhEndCtx = handler(exhStartCtx)
+ normalExitReachable |= !exhEndCtx.bb.ignore
+ exhEndCtx.bb.closeWith(JUMP(normalExitCtx.bb))
+ outerCtx.endHandler()
+ }
}
val bodyCtx = this.newBlock()
+ bodyCtx.bb killIf outerCtx.bb.ignore
if (finalizer != EmptyTree)
bodyCtx.addFinalizer(finalizer, finalizerCtx)
@@ -2019,6 +2062,8 @@ abstract class GenICode extends SubComponent {
outerCtx.bb.closeWith(JUMP(bodyCtx.bb))
+ normalExitReachable |= !bodyEndCtx.bb.ignore
+ normalExitCtx.bb killUnless normalExitReachable
bodyEndCtx.bb.closeWith(JUMP(normalExitCtx.bb))
emitFinalizer(normalExitCtx)
View
3  src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala
@@ -3298,7 +3298,8 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM {
def normalize(m: IMethod) {
if(!m.hasCode) { return }
collapseJumpOnlyBlocks(m)
- elimUnreachableBlocks(m)
+ if (settings.optimise.value)
+ elimUnreachableBlocks(m)
icodes checkValid m
}
View
1  src/compiler/scala/tools/nsc/settings/ScalaSettings.scala
@@ -173,6 +173,7 @@ trait ScalaSettings extends AbsScalaSettings
val Yinvalidate = StringSetting ("-Yinvalidate", "classpath-entry", "Invalidate classpath entry before run", "")
val noSelfCheck = BooleanSetting ("-Yno-self-type-checks", "Suppress check for self-type conformance among inherited members.")
val YvirtClasses = false // too embryonic to even expose as a -Y //BooleanSetting ("-Yvirtual-classes", "Support virtual classes")
+ val YdisableUnreachablePrevention = BooleanSetting("-Ydisable-unreachable-prevention", "Disable the prevention of unreachable blocks in code generation.")
val exposeEmptyPackage = BooleanSetting("-Yexpose-empty-package", "Internal only: expose the empty package.").internalOnly()
View
2  test/files/jvm/t7006/Foo_1.flags
@@ -1 +1 @@
--Ynooptimise -Ydead-code -Ydebug -Xfatal-warnings
+-optimise -Ydebug -Xfatal-warnings
View
1  test/files/jvm/unreachable/Foo_1.flags
@@ -0,0 +1 @@
+-Ynooptimise
View
110 test/files/jvm/unreachable/Foo_1.scala
@@ -0,0 +1,110 @@
+class Foo_1 {
+ def unreachableNormalExit: Int = {
+ return 42
+ 0
+ }
+
+ def unreachableIf: Int = {
+ return 42
+ if (util.Random.nextInt % 2 == 0)
+ 0
+ else
+ 1
+ }
+
+ def unreachableIfBranches: Int = {
+ if (util.Random.nextInt % 2 == 0)
+ return 42
+ else
+ return 42
+
+ return 0
+ }
+
+ def unreachableOneLegIf: Int = {
+ if (util.Random.nextInt % 2 == 0)
+ return 42
+
+ return 42
+ }
+
+ def unreachableLeftBranch: Int = {
+ val result = if (util.Random.nextInt % 2 == 0)
+ return 42
+ else
+ 42
+
+ return result
+ }
+
+ def unreachableRightBranch: Int = {
+ val result = if (util.Random.nextInt % 2 == 0)
+ 42
+ else
+ return 42
+
+ return result
+ }
+
+ def unreachableTryCatchFinally: Int = {
+ return 42
+ try {
+ return 0
+ } catch {
+ case x: Throwable => return 1
+ } finally {
+ return 2
+ }
+ return 3
+ }
+
+ def unreachableAfterTry: Int = {
+ try {
+ return 42
+ } catch {
+ case x: Throwable => return 2
+ }
+ return 3
+ }
+
+ def unreachableAfterCatch: Int = {
+ try {
+ error("haha")
+ } catch {
+ case x: Throwable => return 42
+ }
+ return 3
+ }
+
+ def unreachableAfterFinally: Int = {
+ try {
+ return 1
+ } catch {
+ case x: Throwable => return 2
+ } finally {
+ return 42
+ }
+ return 3
+ }
+
+ def unreachableSwitch: Int = {
+ return 42
+ val x = util.Random.nextInt % 2
+ x match {
+ case 0 => return 0
+ case 1 => return 1
+ case _ => error("wtf")
+ }
+ 2
+ }
+
+ def unreachableAfterSwitch: Int = {
+ val x = util.Random.nextInt % 2
+ x match {
+ case 0 => return 42
+ case 1 => return 41 + x
+ case _ => error("wtf")
+ }
+ 2
+ }
+}
View
23 test/files/jvm/unreachable/Test.scala
@@ -0,0 +1,23 @@
+import scala.tools.partest.BytecodeTest
+import scala.tools.asm
+import asm.tree.InsnList
+import scala.collection.JavaConverters._
+
+object Test extends BytecodeTest {
+ def show: Unit = {
+ val classNode = loadClassNode("Foo_1")
+ // Foo_1 is full of unreachable code which if not elimintated
+ // will result in NOPs as can be confirmed by adding -Ydisable-unreachable-prevention
+ // to Foo_1.flags
+ for (methodNode <- classNode.methods.asScala) {
+ val got = count(methodNode.instructions, asm.Opcodes.NOP)
+ if (got != 0) println(s"Found $got NOP(s) in ${methodNode.name}")
+ }
+ }
+
+ def count(insnList: InsnList, opcode: Int): Int = {
+ def isNop(node: asm.tree.AbstractInsnNode): Boolean =
+ (node.getOpcode == opcode)
+ insnList.iterator.asScala.count(isNop)
+ }
+}
View
152 test/files/run/inline-ex-handlers.check
@@ -14,9 +14,9 @@
<
< 2:
247c246
-< blocks: [1,2,3,4,5,6,7,8,10,11,12,13,14,15,16,17,18]
+< blocks: [1,2,3,4,5,6,7,8,11,12,13,14,15,16,17,18]
---
-> blocks: [1,2,3,4,5,6,8,10,11,12,13,14,15,16,17,18]
+> blocks: [1,2,3,4,5,6,8,11,12,13,14,15,16,17,18]
258,260d256
< 92 JUMP 7
<
@@ -57,19 +57,18 @@
> ? LOAD_LOCAL(value x5)
> 106 CALL_METHOD MyException.message (dynamic)
519c518
-< blocks: [1,2,3,4,6,7,8,9,10]
+< blocks: [1,2,3,4,6,7,9,10]
---
-> blocks: [1,2,3,4,6,7,8,9,10,11,12,13]
-548c547
+> blocks: [1,3,4,6,7,9,10,11,12,13]
+548c547,552
< 306 THROW(MyException)
---
> ? JUMP 11
-549a549,553
+>
> 11:
> ? LOAD_LOCAL(variable monitor4)
> 305 MONITOR_EXIT
> ? JUMP 12
->
554c558
< ? THROW(Throwable)
---
@@ -85,7 +84,13 @@
> 304 MONITOR_EXIT
> ? STORE_LOCAL(value t)
> ? JUMP 13
-575a587,598
+574c585
+< 310 JUMP 2
+---
+> 300 RETURN(UNIT)
+576c587,596
+< 2:
+---
> 13:
> 310 LOAD_MODULE object Predef
> 310 CALL_PRIMITIVE(StartConcat)
@@ -96,37 +101,34 @@
> 310 CALL_PRIMITIVE(StringConcat(REF(class String)))
> 310 CALL_PRIMITIVE(EndConcat)
> 310 CALL_METHOD scala.Predef.println (dynamic)
-> 310 JUMP 2
->
-584c607
-< catch (Throwable) in ArrayBuffer(7, 8, 9, 10) starting at: 6
+584c604
+< catch (Throwable) in ArrayBuffer(7, 9, 10) starting at: 6
---
-> catch (Throwable) in ArrayBuffer(7, 8, 9, 10, 11) starting at: 6
-587c610
-< catch (Throwable) in ArrayBuffer(4, 6, 7, 8, 9, 10) starting at: 3
+> catch (Throwable) in ArrayBuffer(7, 9, 10, 11) starting at: 6
+587c607
+< catch (Throwable) in ArrayBuffer(4, 6, 7, 9, 10) starting at: 3
---
-> catch (Throwable) in ArrayBuffer(4, 6, 7, 8, 9, 10, 11, 12) starting at: 3
-619c642
+> catch (Throwable) in ArrayBuffer(4, 6, 7, 9, 10, 11, 12) starting at: 3
+619c639
< blocks: [1,3,4,5,6,8,9]
---
> blocks: [1,3,4,5,6,8,9,10,11]
-643c666,667
+643c663,669
< 78 THROW(IllegalArgumentException)
---
> ? STORE_LOCAL(value e)
> ? JUMP 10
-644a669,673
+>
> 10:
> 81 LOAD_LOCAL(value e)
> ? STORE_LOCAL(variable exc1)
> ? JUMP 11
->
-669c698,699
+669c695,696
< 81 THROW(Exception)
---
> ? STORE_LOCAL(variable exc1)
> ? JUMP 11
-685a716,728
+685a713,725
> 11:
> 83 LOAD_MODULE object Predef
> 83 CONSTANT("finally")
@@ -140,19 +142,19 @@
> 84 LOAD_LOCAL(variable exc1)
> 84 THROW(Throwable)
>
-691c734
+691c731
< catch (<none>) in ArrayBuffer(4, 5, 6, 8) starting at: 3
---
> catch (<none>) in ArrayBuffer(4, 5, 6, 8, 10) starting at: 3
-715c758
+715c755
< locals: value args, variable result, value ex6, variable exc2, value x4, value x5, value message, value x, value ex6, value x4, value x5, value message, value x
---
> locals: value args, variable result, value ex6, variable exc2, value x4, value x5, value x, value ex6, value x4, value x5, value x
-717c760
+717c757
< blocks: [1,3,4,5,6,9,13,14,15,18,20,21,23,24]
---
> blocks: [1,3,4,5,6,9,13,14,15,18,20,21,23,24,25,26,27]
-741c784,791
+741c781,788
< 172 THROW(MyException)
---
> ? STORE_LOCAL(value ex6)
@@ -163,64 +165,64 @@
> 170 STORE_LOCAL(value x4)
> 170 SCOPE_ENTER value x4
> 170 JUMP 14
-781,784d830
+781,784d827
< 175 LOAD_LOCAL(value x5)
< 175 CALL_METHOD MyException.message (dynamic)
< 175 STORE_LOCAL(value message)
< 175 SCOPE_ENTER value message
-786c832,833
+786c829,830
< 176 LOAD_LOCAL(value message)
---
> ? LOAD_LOCAL(value x5)
> 176 CALL_METHOD MyException.message (dynamic)
-790c837,838
+790c834,835
< 177 LOAD_LOCAL(value message)
---
> ? LOAD_LOCAL(value x5)
> 177 CALL_METHOD MyException.message (dynamic)
-792c840,841
+792c837,838
< 177 THROW(MyException)
---
> ? STORE_LOCAL(value ex6)
> ? JUMP 26
-796c845,846
+796c842,843
< 170 THROW(Throwable)
---
> ? STORE_LOCAL(value ex6)
> ? JUMP 26
-805a856,861
+805a853,858
> 26:
> 169 LOAD_LOCAL(value ex6)
> 169 STORE_LOCAL(value x4)
> 169 SCOPE_ENTER value x4
> 169 JUMP 5
>
-816,819d871
+816,819d868
< 180 LOAD_LOCAL(value x5)
< 180 CALL_METHOD MyException.message (dynamic)
< 180 STORE_LOCAL(value message)
< 180 SCOPE_ENTER value message
-821c873,874
+821c870,871
< 181 LOAD_LOCAL(value message)
---
> ? LOAD_LOCAL(value x5)
> 181 CALL_METHOD MyException.message (dynamic)
-825c878,879
+825c875,876
< 182 LOAD_LOCAL(value message)
---
> ? LOAD_LOCAL(value x5)
> 182 CALL_METHOD MyException.message (dynamic)
-827c881,882
+827c878,879
< 182 THROW(MyException)
---
> ? STORE_LOCAL(variable exc2)
> ? JUMP 27
-831c886,887
+831c883,884
< 169 THROW(Throwable)
---
> ? STORE_LOCAL(variable exc2)
> ? JUMP 27
-847a904,916
+847a901,913
> 27:
> 184 LOAD_MODULE object Predef
> 184 CONSTANT("finally")
@@ -234,23 +236,23 @@
> 185 LOAD_LOCAL(variable exc2)
> 185 THROW(Throwable)
>
-853c922
+853c919
< catch (Throwable) in ArrayBuffer(13, 14, 15, 18, 20, 21, 23) starting at: 4
---
> catch (Throwable) in ArrayBuffer(13, 14, 15, 18, 20, 21, 23, 25) starting at: 4
-856c925
+856c922
< catch (<none>) in ArrayBuffer(4, 5, 6, 9, 13, 14, 15, 18, 20, 21, 23) starting at: 3
---
> catch (<none>) in ArrayBuffer(4, 5, 6, 9, 13, 14, 15, 18, 20, 21, 23, 25, 26) starting at: 3
-880c949
+880c946
< locals: value args, variable result, value e, value ex6, value x4, value x5, value message, value x
---
> locals: value args, variable result, value e, value ex6, value x4, value x5, value x
-882c951
+882c948
< blocks: [1,2,3,6,7,8,11,13,14,16]
---
> blocks: [1,2,3,6,7,8,11,13,14,16,17]
-906c975,982
+906c972,979
< 124 THROW(MyException)
---
> ? STORE_LOCAL(value ex6)
@@ -261,29 +263,29 @@
> 122 STORE_LOCAL(value x4)
> 122 SCOPE_ENTER value x4
> 122 JUMP 7
-931,934d1006
+931,934d1003
< 127 LOAD_LOCAL(value x5)
< 127 CALL_METHOD MyException.message (dynamic)
< 127 STORE_LOCAL(value message)
< 127 SCOPE_ENTER value message
-936c1008,1009
+936c1005,1006
< 127 LOAD_LOCAL(value message)
---
> ? LOAD_LOCAL(value x5)
> 127 CALL_METHOD MyException.message (dynamic)
-965c1038
+965c1035
< catch (IllegalArgumentException) in ArrayBuffer(6, 7, 8, 11, 13, 14, 16) starting at: 3
---
> catch (IllegalArgumentException) in ArrayBuffer(6, 7, 8, 11, 13, 14, 16, 17) starting at: 3
-989c1062
+989c1059
< locals: value args, variable result, value ex6, value x4, value x5, value message, value x, value e
---
> locals: value args, variable result, value ex6, value x4, value x5, value x, value e
-991c1064
+991c1061
< blocks: [1,2,3,4,5,8,12,13,14,16]
---
> blocks: [1,2,3,5,8,12,13,14,16,17]
-1015c1088,1097
+1015c1085,1094
< 148 THROW(MyException)
---
> ? STORE_LOCAL(value ex6)
@@ -296,25 +298,25 @@
> 154 LOAD_LOCAL(value x4)
> 154 IS_INSTANCE REF(class MyException)
> 154 CZJUMP (BOOL)NE ? 5 : 8
-1036,1038d1117
+1036,1038d1114
< 145 JUMP 4
<
< 4:
-1048,1051d1126
+1048,1051d1123
< 154 LOAD_LOCAL(value x5)
< 154 CALL_METHOD MyException.message (dynamic)
< 154 STORE_LOCAL(value message)
< 154 SCOPE_ENTER value message
-1053c1128,1129
+1053c1125,1126
< 154 LOAD_LOCAL(value message)
---
> ? LOAD_LOCAL(value x5)
> 154 CALL_METHOD MyException.message (dynamic)
-1270c1346
+1270c1343
< blocks: [1,2,3,4,5,7]
---
> blocks: [1,2,3,4,5,7,8]
-1294c1370,1377
+1294c1367,1374
< 38 THROW(IllegalArgumentException)
---
> ? STORE_LOCAL(value e)
@@ -325,20 +327,20 @@
> 42 CONSTANT("IllegalArgumentException")
> 42 CALL_METHOD scala.Predef.println (dynamic)
> 42 JUMP 2
-1341c1424
+1341c1421
< locals: value args, variable result, value ex6, value x4, value x5, value message, value x
---
> locals: value args, variable result, value ex6, value x4, value x5, value x
-1343c1426
+1343c1423
< blocks: [1,2,3,4,5,8,10,11,13,14,16]
---
> blocks: [1,2,3,5,8,10,11,13,14,16,17]
-1367c1450,1451
+1367c1447,1448
< 203 THROW(MyException)
---
> ? STORE_LOCAL(value ex6)
> ? JUMP 17
-1387c1471,1480
+1387c1468,1477
< 209 THROW(MyException)
---
> ? STORE_LOCAL(value ex6)
@@ -351,41 +353,41 @@
> 212 LOAD_LOCAL(value x4)
> 212 IS_INSTANCE REF(class MyException)
> 212 CZJUMP (BOOL)NE ? 5 : 8
-1400,1402d1492
+1400,1402d1489
< 200 JUMP 4
<
< 4:
-1412,1415d1501
+1412,1415d1498
< 212 LOAD_LOCAL(value x5)
< 212 CALL_METHOD MyException.message (dynamic)
< 212 STORE_LOCAL(value message)
< 212 SCOPE_ENTER value message
-1417c1503,1504
+1417c1500,1501
< 213 LOAD_LOCAL(value message)
---
> ? LOAD_LOCAL(value x5)
> 213 CALL_METHOD MyException.message (dynamic)
-1461c1548
+1461c1545
< blocks: [1,2,3,4,5,7]
---
> blocks: [1,2,3,4,5,7,8]
-1485c1572,1573
+1485c1569,1570
< 58 THROW(IllegalArgumentException)
---
> ? STORE_LOCAL(value e)
> ? JUMP 8
-1486a1575,1580
+1486a1572,1577
> 8:
> 62 LOAD_MODULE object Predef
> 62 CONSTANT("RuntimeException")
> 62 CALL_METHOD scala.Predef.println (dynamic)
> 62 JUMP 2
>
-1534c1628
-< blocks: [1,2,3,4]
+1534c1625
+< blocks: [1,3,4]
---
-> blocks: [1,2,3,4,5]
-1554c1648,1653
+> blocks: [1,3,4,5]
+1554c1645,1650
< 229 THROW(MyException)
---
> ? JUMP 5
@@ -394,19 +396,19 @@
> ? LOAD_LOCAL(variable monitor1)
> 228 MONITOR_EXIT
> 228 THROW(Throwable)
-1560c1659
+1560c1656
< ? THROW(Throwable)
---
> 228 THROW(Throwable)
-1588c1687
+1588c1684
< locals: value args, variable result, variable monitor2, variable monitorResult1
---
> locals: value exception$1, value args, variable result, variable monitor2, variable monitorResult1
-1590c1689
-< blocks: [1,2,3,4]
+1590c1686
+< blocks: [1,3,4]
---
-> blocks: [1,2,3,4,5]
-1613c1712,1720
+> blocks: [1,3,4,5]
+1613c1709,1717
< 245 THROW(MyException)
---
> ? STORE_LOCAL(value exception$1)
@@ -418,7 +420,7 @@
> ? LOAD_LOCAL(variable monitor2)
> 244 MONITOR_EXIT
> 244 THROW(Throwable)
-1619c1726
+1619c1723
< ? THROW(Throwable)
---
> 244 THROW(Throwable)
View
125 test/files/run/unreachable.scala
@@ -0,0 +1,125 @@
+object Test extends App {
+ def unreachableNormalExit: Int = {
+ return 42
+ 0
+ }
+
+ def unreachableIf: Int = {
+ return 42
+ if (util.Random.nextInt % 2 == 0)
+ 0
+ else
+ 1
+ }
+
+ def unreachableIfBranches: Int = {
+ if (util.Random.nextInt % 2 == 0)
+ return 42
+ else
+ return 42
+
+ return 0
+ }
+
+ def unreachableOneLegIf: Int = {
+ if (util.Random.nextInt % 2 == 0)
+ return 42
+
+ return 42
+ }
+
+ def unreachableLeftBranch: Int = {
+ val result = if (util.Random.nextInt % 2 == 0)
+ return 42
+ else
+ 42
+
+ return result
+ }
+
+ def unreachableRightBranch: Int = {
+ val result = if (util.Random.nextInt % 2 == 0)
+ 42
+ else
+ return 42
+
+ return result
+ }
+
+ def unreachableTryCatchFinally: Int = {
+ return 42
+ try {
+ return 0
+ } catch {
+ case x: Throwable => return 1
+ } finally {
+ return 2
+ }
+ return 3
+ }
+
+ def unreachableAfterTry: Int = {
+ try {
+ return 42
+ } catch {
+ case x: Throwable => return 2
+ }
+ return 3
+ }
+
+ def unreachableAfterCatch: Int = {
+ try {
+ error("haha")
+ } catch {
+ case x: Throwable => return 42
+ }
+ return 3
+ }
+
+ def unreachableAfterFinally: Int = {
+ try {
+ return 1
+ } catch {
+ case x: Throwable => return 2
+ } finally {
+ return 42
+ }
+ return 3
+ }
+
+ def unreachableSwitch: Int = {
+ return 42
+ val x = util.Random.nextInt % 2
+ x match {
+ case 0 => return 0
+ case 1 => return 1
+ case _ => error("wtf")
+ }
+ 2
+ }
+
+ def unreachableAfterSwitch: Int = {
+ val x = util.Random.nextInt % 2
+ x match {
+ case 0 => return 42
+ case 1 => return 41 + x
+ case _ => error("wtf")
+ }
+ 2
+ }
+
+ def check(f: Int) = assert(f == 42, s"Expected 42 but got $f")
+
+ check(unreachableNormalExit)
+ check(unreachableIf)
+ check(unreachableIfBranches)
+ check(unreachableOneLegIf)
+ check(unreachableLeftBranch)
+ check(unreachableRightBranch)
+ check(unreachableTryCatchFinally)
+ check(unreachableAfterTry)
+ check(unreachableAfterCatch)
+ check(unreachableAfterFinally)
+ check(unreachableSwitch)
+ check(unreachableAfterSwitch)
+}
Please sign in to comment.
Something went wrong with that request. Please try again.