Refactor try-catch covering computation
This commit is contained in:
+28
-47
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user