Refactor try-catch covering computation

This commit is contained in:
pyos
2019-03-27 14:10:04 +01:00
committed by max-kammerer
parent a8e2893494
commit 89cb31c387
@@ -58,12 +58,14 @@ import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
import java.util.* import java.util.*
open class ExpressionInfo(val expression: IrExpression) sealed class ExpressionInfo()
class LoopInfo(val loop: IrLoop, val continueLabel: Label, val breakLabel: Label) : ExpressionInfo(loop) class LoopInfo(val loop: IrLoop, val continueLabel: Label, val breakLabel: Label) : ExpressionInfo()
class TryInfo(val tryBlock: IrTry) : ExpressionInfo(tryBlock) { class TryInfo(val onExit: IrExpression) : ExpressionInfo() {
val gaps = mutableListOf<Label>() // Regions corresponding to copy-pasted contents of the `finally` block.
// These should not be covered by `catch` clauses.
val gaps = mutableListOf<Pair<Label, Label>>()
} }
class BlockInfo private constructor(val parent: BlockInfo?) { class BlockInfo private constructor(val parent: BlockInfo?) {
@@ -76,7 +78,7 @@ class BlockInfo private constructor(val parent: BlockInfo?) {
fun hasFinallyBlocks(): Boolean = infos.firstIsInstanceOrNull<TryInfo>() != null fun hasFinallyBlocks(): Boolean = infos.firstIsInstanceOrNull<TryInfo>() != null
inline fun <R> withBlock(info: ExpressionInfo, f: (ExpressionInfo) -> R): R { inline fun <T : ExpressionInfo, R> withBlock(info: T, f: (T) -> R): R {
infos.add(info) infos.add(info)
try { try {
return f(info) return f(info)
@@ -1021,7 +1023,7 @@ class ExpressionCodegen(
override fun visitTry(aTry: IrTry, data: BlockInfo): StackValue { override fun visitTry(aTry: IrTry, data: BlockInfo): StackValue {
aTry.markLineNumber(startOffset = true) aTry.markLineNumber(startOffset = true)
return if (aTry.finallyExpression != null) return if (aTry.finallyExpression != null)
data.withBlock(TryInfo(aTry)) { visitTryWithInfo(aTry, data, it as TryInfo) } data.withBlock(TryInfo(aTry.finallyExpression!!)) { visitTryWithInfo(aTry, data, it) }
else else
visitTryWithInfo(aTry, data, null) visitTryWithInfo(aTry, data, null)
} }
@@ -1039,9 +1041,7 @@ class ExpressionCodegen(
} }
val tryBlockEnd = markNewLabel() val tryBlockEnd = markNewLabel()
val tryBlockGaps = tryInfo?.gaps?.toList() ?: listOf()
val tryRegions = getCurrentTryIntervals(tryInfo, tryBlockStart, tryBlockEnd)
val tryCatchBlockEnd = Label() val tryCatchBlockEnd = Label()
if (tryInfo != null) { if (tryInfo != null) {
data.handleBlock { genFinallyBlock(tryInfo, tryCatchBlockEnd, null, data) } data.handleBlock { genFinallyBlock(tryInfo, tryCatchBlockEnd, null, data) }
@@ -1080,28 +1080,27 @@ class ExpressionCodegen(
mv.goTo(tryCatchBlockEnd) mv.goTo(tryCatchBlockEnd)
} }
generateExceptionTable(clauseStart, tryRegions, descriptorType.internalName) genTryCatchCover(clauseStart, tryBlockStart, tryBlockEnd, tryBlockGaps, descriptorType.internalName)
} }
//for default catch clause
if (tryInfo != null) { if (tryInfo != null) {
// Generate `try { ... } catch (e: Any?) { <finally>; throw e }` around every part of
// the try-catch that is not a copy-pasted `finally` block.
val defaultCatchStart = markNewLabel() val defaultCatchStart = markNewLabel()
// While keeping this value on the stack should be enough, the bytecode validator will
// complain if a catch block does not start with ASTORE.
val savedException = frame.enterTemp(JAVA_THROWABLE_TYPE) val savedException = frame.enterTemp(JAVA_THROWABLE_TYPE)
mv.store(savedException, JAVA_THROWABLE_TYPE) mv.store(savedException, JAVA_THROWABLE_TYPE)
val defaultCatchEnd = markNewLabel()
//do it before finally block generation
//javac also generates entry in exception table for default catch clause too!!!! so defaultCatchEnd as end parameter
val defaultCatchRegions = getCurrentTryIntervals(tryInfo, tryBlockStart, defaultCatchEnd)
val finallyStart = markNewLabel()
val finallyGaps = tryInfo.gaps.toList()
data.handleBlock { genFinallyBlock(tryInfo, null, null, data) } data.handleBlock { genFinallyBlock(tryInfo, null, null, data) }
mv.load(savedException, JAVA_THROWABLE_TYPE) mv.load(savedException, JAVA_THROWABLE_TYPE)
frame.leaveTemp(JAVA_THROWABLE_TYPE) frame.leaveTemp(JAVA_THROWABLE_TYPE)
mv.athrow() mv.athrow()
generateExceptionTable(defaultCatchStart, defaultCatchRegions, null) // Include the ASTORE into the covered region. This is used by the inliner to detect try-finally.
genTryCatchCover(defaultCatchStart, tryBlockStart, finallyStart, finallyGaps, null)
} }
mv.mark(tryCatchBlockEnd) mv.mark(tryCatchBlockEnd)
@@ -1110,52 +1109,34 @@ class ExpressionCodegen(
savedValue.put(mv) savedValue.put(mv)
frame.leaveTemp(aTry.asmType) frame.leaveTemp(aTry.asmType)
} }
// TODO: generate a common `finally` for try & catch blocks here? Right now this breaks the inliner.
return aTry.onStack return aTry.onStack
} }
private fun getCurrentTryIntervals( private fun genTryCatchCover(catchStart: Label, tryStart: Label, tryEnd: Label, tryGaps: List<Pair<Label, Label>>, type: String?) {
finallyBlockStackElement: TryInfo?, val lastRegionStart = tryGaps.fold(tryStart) { regionStart, (gapStart, gapEnd) ->
blockStart: Label, mv.visitTryCatchBlock(regionStart, gapStart, catchStart, type)
blockEnd: Label gapEnd
): List<Label> {
val gapsInBlock = if (finallyBlockStackElement != null) ArrayList<Label>(finallyBlockStackElement.gaps) else emptyList<Label>()
assert(gapsInBlock.size % 2 == 0)
val blockRegions = ArrayList<Label>(gapsInBlock.size + 2)
blockRegions.add(blockStart)
blockRegions.addAll(gapsInBlock)
blockRegions.add(blockEnd)
return blockRegions
}
private fun generateExceptionTable(catchStart: Label, catchedRegions: List<Label>, exception: String?) {
var i = 0
while (i < catchedRegions.size) {
val startRegion = catchedRegions[i]
val endRegion = catchedRegions[i + 1]
mv.visitTryCatchBlock(startRegion, endRegion, catchStart, exception)
i += 2
} }
mv.visitTryCatchBlock(lastRegionStart, tryEnd, catchStart, type)
} }
private fun genFinallyBlock(tryInfo: TryInfo, tryCatchBlockEnd: Label?, afterJumpLabel: Label?, data: BlockInfo) { private fun genFinallyBlock(tryInfo: TryInfo, tryCatchBlockEnd: Label?, afterJumpLabel: Label?, data: BlockInfo) {
assert(tryInfo.gaps.size % 2 == 0) { "Finally block gaps are inconsistent" } val gapStart = markNewLabel()
tryInfo.gaps.add(markNewLabel())
finallyDepth++ finallyDepth++
if (isFinallyMarkerRequired()) { if (isFinallyMarkerRequired()) {
generateFinallyMarker(mv, finallyDepth, true) generateFinallyMarker(mv, finallyDepth, true)
} }
gen(tryInfo.tryBlock.finallyExpression!!, data).discard() gen(tryInfo.onExit, data).discard()
if (isFinallyMarkerRequired()) { if (isFinallyMarkerRequired()) {
generateFinallyMarker(mv, finallyDepth, false) generateFinallyMarker(mv, finallyDepth, false)
} }
finallyDepth-- finallyDepth--
if (tryCatchBlockEnd != null) { if (tryCatchBlockEnd != null) {
tryInfo.tryBlock.finallyExpression!!.markLineNumber(startOffset = false) tryInfo.onExit.markLineNumber(startOffset = false)
mv.goTo(tryCatchBlockEnd) mv.goTo(tryCatchBlockEnd)
} }
tryInfo.gaps.add(afterJumpLabel ?: markNewLabel()) tryInfo.gaps.add(gapStart to (afterJumpLabel ?: markNewLabel()))
} }
fun generateFinallyBlocksIfNeeded(returnType: Type, afterReturnLabel: Label, data: BlockInfo) { fun generateFinallyBlocksIfNeeded(returnType: Type, afterReturnLabel: Label, data: BlockInfo) {