[Coverage] Support jump expressions
This commit is contained in:
committed by
Sergey Bogolepov
parent
34323be304
commit
863d8a9208
+33
-14
@@ -9,6 +9,7 @@ import org.jetbrains.kotlin.backend.konan.llvm.column
|
|||||||
import org.jetbrains.kotlin.backend.konan.llvm.line
|
import org.jetbrains.kotlin.backend.konan.llvm.line
|
||||||
import org.jetbrains.kotlin.backend.konan.llvm.symbolName
|
import org.jetbrains.kotlin.backend.konan.llvm.symbolName
|
||||||
import org.jetbrains.kotlin.ir.IrElement
|
import org.jetbrains.kotlin.ir.IrElement
|
||||||
|
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
|
||||||
import org.jetbrains.kotlin.ir.declarations.IrFile
|
import org.jetbrains.kotlin.ir.declarations.IrFile
|
||||||
import org.jetbrains.kotlin.ir.declarations.IrFunction
|
import org.jetbrains.kotlin.ir.declarations.IrFunction
|
||||||
import org.jetbrains.kotlin.ir.declarations.name
|
import org.jetbrains.kotlin.ir.declarations.name
|
||||||
@@ -19,27 +20,45 @@ import org.jetbrains.kotlin.ir.declarations.name
|
|||||||
* Besides the obvious [file] and line/column borders, it has [RegionKind] which is described later.
|
* Besides the obvious [file] and line/column borders, it has [RegionKind] which is described later.
|
||||||
*/
|
*/
|
||||||
class Region(
|
class Region(
|
||||||
|
startOffset: Int,
|
||||||
|
endOffset: Int,
|
||||||
val file: IrFile,
|
val file: IrFile,
|
||||||
val startLine: Int,
|
|
||||||
val startColumn: Int,
|
|
||||||
val endLine: Int,
|
|
||||||
val endColumn: Int,
|
|
||||||
val kind: RegionKind
|
val kind: RegionKind
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
var startOffset: Int = startOffset
|
||||||
|
set(value) {
|
||||||
|
if (value != UNDEFINED_OFFSET) {
|
||||||
|
field = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var endOffset: Int = endOffset
|
||||||
|
set(value) {
|
||||||
|
if (value != UNDEFINED_OFFSET) {
|
||||||
|
field = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val startLine: Int
|
||||||
|
get() = file.fileEntry.line(startOffset)
|
||||||
|
|
||||||
|
val startColumn: Int
|
||||||
|
get() = file.fileEntry.column(startOffset)
|
||||||
|
|
||||||
|
val endLine: Int
|
||||||
|
get() = file.fileEntry.line(endOffset)
|
||||||
|
|
||||||
|
val endColumn: Int
|
||||||
|
get() = file.fileEntry.column(endOffset)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun fromIr(irElement: IrElement, irFile: IrFile, kind: RegionKind) =
|
fun fromIr(irElement: IrElement, irFile: IrFile, kind: RegionKind = RegionKind.Code) =
|
||||||
fromOffset(irElement.startOffset, irElement.endOffset, irFile, kind)
|
fromOffset(irElement.startOffset, irElement.endOffset, irFile, kind)
|
||||||
|
|
||||||
fun fromOffset(startOffset: Int, endOffset: Int, irFile: IrFile, kind: RegionKind) =
|
fun fromOffset(startOffset: Int, endOffset: Int, irFile: IrFile, kind: RegionKind = RegionKind.Code) =
|
||||||
Region(
|
if (startOffset == UNDEFINED_OFFSET || endOffset == UNDEFINED_OFFSET || startOffset == endOffset) null
|
||||||
irFile,
|
else Region(startOffset, endOffset, irFile, kind)
|
||||||
irFile.fileEntry.line(startOffset),
|
|
||||||
irFile.fileEntry.column(startOffset),
|
|
||||||
irFile.fileEntry.line(endOffset),
|
|
||||||
irFile.fileEntry.column(endOffset),
|
|
||||||
kind
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
|
|||||||
+86
-33
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.backend.konan.llvm.coverage
|
|||||||
import org.jetbrains.kotlin.backend.common.pop
|
import org.jetbrains.kotlin.backend.common.pop
|
||||||
import org.jetbrains.kotlin.backend.common.push
|
import org.jetbrains.kotlin.backend.common.push
|
||||||
import org.jetbrains.kotlin.ir.IrElement
|
import org.jetbrains.kotlin.ir.IrElement
|
||||||
|
import org.jetbrains.kotlin.ir.IrStatement
|
||||||
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
|
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
|
||||||
import org.jetbrains.kotlin.ir.declarations.*
|
import org.jetbrains.kotlin.ir.declarations.*
|
||||||
import org.jetbrains.kotlin.ir.expressions.*
|
import org.jetbrains.kotlin.ir.expressions.*
|
||||||
@@ -71,35 +72,63 @@ private class IrFunctionRegionsCollector(
|
|||||||
val irFile: IrFile
|
val irFile: IrFile
|
||||||
) : IrElementVisitorVoid {
|
) : IrElementVisitorVoid {
|
||||||
|
|
||||||
|
private data class StatementContext(val current: IrStatement, val next: IrStatement?)
|
||||||
|
|
||||||
|
val regions = mutableMapOf<IrElement, Region>()
|
||||||
|
|
||||||
private val irFileStack = mutableListOf(irFile)
|
private val irFileStack = mutableListOf(irFile)
|
||||||
|
|
||||||
|
private val regionStack = mutableListOf<Region>()
|
||||||
|
|
||||||
|
private val irStatementsStack = mutableListOf<StatementContext>()
|
||||||
|
|
||||||
private val currentFile: IrFile
|
private val currentFile: IrFile
|
||||||
get() = irFileStack.last()
|
get() = irFileStack.last()
|
||||||
|
|
||||||
val regions = mutableMapOf<IrElement, Region>()
|
private val currentRegion: Region
|
||||||
|
get() = regionStack.last()
|
||||||
|
|
||||||
override fun visitElement(element: IrElement) {
|
override fun visitElement(element: IrElement) {
|
||||||
element.acceptChildrenVoid(this)
|
element.acceptChildrenVoid(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visitSimpleFunction(declaration: IrSimpleFunction) {
|
override fun visitSimpleFunction(declaration: IrSimpleFunction) {
|
||||||
declaration.body?.let {
|
declaration.body?.let { body ->
|
||||||
recordRegion(it)
|
visitInRegionContext(recordRegion(body) ?: return) {
|
||||||
it.acceptChildrenVoid(this)
|
body.acceptVoid(this)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visitConstructor(declaration: IrConstructor) {
|
override fun visitConstructor(declaration: IrConstructor) {
|
||||||
val statements = declaration.body?.statements ?: return
|
val statements = declaration.body?.statements ?: return
|
||||||
statements.forEach {
|
visitInStatementContext(statements) { statement ->
|
||||||
if (it is IrDelegatingConstructorCall && !declaration.isPrimary
|
if (statement is IrDelegatingConstructorCall && !declaration.isPrimary
|
||||||
|| it !is IrDelegatingConstructorCall && it !is IrReturn) {
|
|| statement !is IrDelegatingConstructorCall && statement !is IrReturn) {
|
||||||
recordRegion(it)
|
recordRegion(statement)
|
||||||
it.acceptVoid(this)
|
statement.acceptVoid(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun visitBody(body: IrBody) = visitInStatementContext(body.statements)
|
||||||
|
|
||||||
|
override fun visitContainerExpression(expression: IrContainerExpression) {
|
||||||
|
val statements = expression.statements
|
||||||
|
when (expression) {
|
||||||
|
is IrReturnableBlock -> {
|
||||||
|
val file = expression.sourceFileSymbol?.owner
|
||||||
|
if (file != null && file != currentFile && fileFilter(file)) {
|
||||||
|
recordRegion(expression)
|
||||||
|
visitInFileContext(file) {
|
||||||
|
visitInStatementContext(statements)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> visitInStatementContext(statements)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: The following implementation produces correct region mapping, but something goes wrong later
|
// TODO: The following implementation produces correct region mapping, but something goes wrong later
|
||||||
// override fun visitFieldAccess(expression: IrFieldAccessExpression) {
|
// override fun visitFieldAccess(expression: IrFieldAccessExpression) {
|
||||||
// expression.receiver?.let { recordRegion(it) }
|
// expression.receiver?.let { recordRegion(it) }
|
||||||
@@ -117,48 +146,72 @@ private class IrFunctionRegionsCollector(
|
|||||||
} else {
|
} else {
|
||||||
recordRegion(condition)
|
recordRegion(condition)
|
||||||
recordRegion(result, condition.endOffset, result.endOffset)
|
recordRegion(result, condition.endOffset, result.endOffset)
|
||||||
condition.acceptChildrenVoid(this)
|
condition.acceptVoid(this)
|
||||||
}
|
}
|
||||||
result.acceptChildrenVoid(this)
|
result.acceptVoid(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visitLoop(loop: IrLoop) {
|
override fun visitLoop(loop: IrLoop) {
|
||||||
val condition = loop.condition
|
val condition = loop.condition
|
||||||
recordRegion(condition)
|
recordRegion(condition)
|
||||||
condition.acceptChildrenVoid(this)
|
condition.acceptVoid(this)
|
||||||
|
|
||||||
val body = loop.body ?: return
|
val body = loop.body ?: return
|
||||||
when (loop) {
|
when (loop) {
|
||||||
is IrWhileLoop -> recordRegion(body, condition.endOffset, body.endOffset)
|
is IrWhileLoop -> recordRegion(body, condition.endOffset, body.endOffset)
|
||||||
is IrDoWhileLoop -> recordRegion(body, body.startOffset, condition.startOffset)
|
is IrDoWhileLoop -> recordRegion(body, body.startOffset, condition.startOffset)
|
||||||
}
|
}
|
||||||
body.acceptChildrenVoid(this)
|
body.acceptVoid(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visitBlock(expression: IrBlock) {
|
override fun visitReturn(expression: IrReturn) {
|
||||||
when (expression) {
|
val next = irStatementsStack.lastOrNull()?.next ?: return
|
||||||
is IrReturnableBlock -> {
|
val nextRegion = recordRegion(next, expression.endOffset, currentRegion.endOffset) ?: return
|
||||||
val file = (expression.sourceFileSymbol?.owner)
|
currentRegion.endOffset = expression.endOffset
|
||||||
if (file != null && file != currentFile && fileFilter(file)) {
|
regionStack.pop()
|
||||||
recordRegion(expression)
|
regionStack.push(nextRegion)
|
||||||
irFileStack.push(file)
|
}
|
||||||
expression.acceptChildrenVoid(this)
|
|
||||||
irFileStack.pop()
|
override fun visitBreakContinue(jump: IrBreakContinue) {
|
||||||
}
|
val (current, next) = irStatementsStack.lastOrNull() ?: return
|
||||||
|
recordRegion(next ?: return, current.endOffset, jump.loop.endOffset)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun visitInFileContext(file: IrFile, visit: () -> Unit) {
|
||||||
|
irFileStack.push(file)
|
||||||
|
visit()
|
||||||
|
irFileStack.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun visitInRegionContext(region: Region, visit: () -> Unit) {
|
||||||
|
regionStack.push(region)
|
||||||
|
visit()
|
||||||
|
regionStack.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun visitInStatementContext(
|
||||||
|
statements: List<IrStatement>,
|
||||||
|
visit: (IrStatement) -> Unit = { statement -> statement.acceptVoid(this) }
|
||||||
|
) {
|
||||||
|
for (i in 0..statements.lastIndex) {
|
||||||
|
val current = statements[i]
|
||||||
|
if (!current.hasValidOffsets()) {
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
else -> expression.acceptChildrenVoid(this)
|
val next = if (i < statements.lastIndex && statements[i + 1].hasValidOffsets()) statements[i + 1] else null
|
||||||
|
irStatementsStack.push(StatementContext(current, next))
|
||||||
|
visit(current)
|
||||||
|
irStatementsStack.pop()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun recordRegion(irElement: IrElement, kind: RegionKind = RegionKind.Code) {
|
private fun recordRegion(irElement: IrElement, kind: RegionKind = RegionKind.Code)
|
||||||
recordRegion(irElement, irElement.startOffset, irElement.endOffset, kind)
|
= Region.fromIr(irElement, currentFile, kind)?.also { regions[irElement] = it }
|
||||||
}
|
|
||||||
|
|
||||||
private fun recordRegion(irElement: IrElement, startOffset: Int, endOffset: Int, kind: RegionKind = RegionKind.Code) {
|
private fun recordRegion(irElement: IrElement, startOffset: Int, endOffset: Int, kind: RegionKind = RegionKind.Code)
|
||||||
if (startOffset == UNDEFINED_OFFSET || endOffset == UNDEFINED_OFFSET) {
|
= Region.fromOffset(startOffset, endOffset, currentFile, kind)?.also { regions[irElement] = it }
|
||||||
return
|
|
||||||
}
|
private fun IrElement.hasValidOffsets() = startOffset != UNDEFINED_OFFSET && endOffset != UNDEFINED_OFFSET
|
||||||
regions[irElement] = Region.fromOffset(startOffset, endOffset, currentFile, kind)
|
&& startOffset != endOffset
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -3961,6 +3961,21 @@ if (UtilsKt.testTargetSupportsCodeCoverage(project)) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
task coverage_jumps(type: CoverageTest) {
|
||||||
|
binaryName = "CoverageJumps"
|
||||||
|
numberOfCoveredFunctions = 7
|
||||||
|
numberOfCoveredLines = 66
|
||||||
|
|
||||||
|
konanArtifacts {
|
||||||
|
program(binaryName, targets: [ target ]) {
|
||||||
|
srcDir 'coverage/basic/jumps'
|
||||||
|
baseDir "$testOutputCoverage/$binaryName"
|
||||||
|
entryPoint "coverage.basic.jumps.main"
|
||||||
|
extraOpts "-Xcoverage", "-Xcoverage-file=$profrawFile"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
task coverage_smoke0(type: CoverageTest) {
|
task coverage_smoke0(type: CoverageTest) {
|
||||||
binaryName = "CoverageSmoke0"
|
binaryName = "CoverageSmoke0"
|
||||||
numberOfCoveredFunctions = 2
|
numberOfCoveredFunctions = 2
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
package coverage.basic.jumps
|
||||||
|
|
||||||
|
fun simpleReturn(n: Int) {
|
||||||
|
if (n == 0) return
|
||||||
|
println(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun returnFromIfBranch(n: Int) {
|
||||||
|
if (n > 0) {
|
||||||
|
if (n > 10) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else if (n < -10) {
|
||||||
|
return
|
||||||
|
} else if (n == 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
println(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun returnFromWhenBranch(n: Int) {
|
||||||
|
when {
|
||||||
|
n == 0 -> return
|
||||||
|
n == 1 -> return
|
||||||
|
n == 2 -> {
|
||||||
|
println(n)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
else -> println(n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun breakFromWhile() {
|
||||||
|
var a = 7
|
||||||
|
while (true) {
|
||||||
|
if (a < 4) break
|
||||||
|
println(a)
|
||||||
|
a--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun continueFromDoWhile() {
|
||||||
|
var a = 0
|
||||||
|
do {
|
||||||
|
if (a % 3 == 0) {
|
||||||
|
a++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
println(a)
|
||||||
|
a++
|
||||||
|
} while (a < 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun singleReturn() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
simpleReturn(0)
|
||||||
|
simpleReturn(1)
|
||||||
|
|
||||||
|
returnFromIfBranch(1)
|
||||||
|
returnFromIfBranch(11)
|
||||||
|
returnFromIfBranch(-11)
|
||||||
|
returnFromIfBranch(0)
|
||||||
|
|
||||||
|
returnFromWhenBranch(0)
|
||||||
|
returnFromWhenBranch(1)
|
||||||
|
returnFromWhenBranch(2)
|
||||||
|
|
||||||
|
breakFromWhile()
|
||||||
|
continueFromDoWhile()
|
||||||
|
singleReturn()
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user