[Coverage] Support jump expressions

This commit is contained in:
Anastasiya Shadrina
2019-12-16 11:12:04 +07:00
committed by Sergey Bogolepov
parent 34323be304
commit 863d8a9208
4 changed files with 208 additions and 47 deletions
@@ -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.symbolName
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.IrFunction
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.
*/
class Region(
startOffset: Int,
endOffset: Int,
val file: IrFile,
val startLine: Int,
val startColumn: Int,
val endLine: Int,
val endColumn: Int,
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 {
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)
fun fromOffset(startOffset: Int, endOffset: Int, irFile: IrFile, kind: RegionKind) =
Region(
irFile,
irFile.fileEntry.line(startOffset),
irFile.fileEntry.column(startOffset),
irFile.fileEntry.line(endOffset),
irFile.fileEntry.column(endOffset),
kind
)
fun fromOffset(startOffset: Int, endOffset: Int, irFile: IrFile, kind: RegionKind = RegionKind.Code) =
if (startOffset == UNDEFINED_OFFSET || endOffset == UNDEFINED_OFFSET || startOffset == endOffset) null
else Region(startOffset, endOffset, irFile, kind)
}
override fun toString(): String {
@@ -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.push
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.*
@@ -71,35 +72,63 @@ private class IrFunctionRegionsCollector(
val irFile: IrFile
) : IrElementVisitorVoid {
private data class StatementContext(val current: IrStatement, val next: IrStatement?)
val regions = mutableMapOf<IrElement, Region>()
private val irFileStack = mutableListOf(irFile)
private val regionStack = mutableListOf<Region>()
private val irStatementsStack = mutableListOf<StatementContext>()
private val currentFile: IrFile
get() = irFileStack.last()
val regions = mutableMapOf<IrElement, Region>()
private val currentRegion: Region
get() = regionStack.last()
override fun visitElement(element: IrElement) {
element.acceptChildrenVoid(this)
}
override fun visitSimpleFunction(declaration: IrSimpleFunction) {
declaration.body?.let {
recordRegion(it)
it.acceptChildrenVoid(this)
declaration.body?.let { body ->
visitInRegionContext(recordRegion(body) ?: return) {
body.acceptVoid(this)
}
}
}
override fun visitConstructor(declaration: IrConstructor) {
val statements = declaration.body?.statements ?: return
statements.forEach {
if (it is IrDelegatingConstructorCall && !declaration.isPrimary
|| it !is IrDelegatingConstructorCall && it !is IrReturn) {
recordRegion(it)
it.acceptVoid(this)
visitInStatementContext(statements) { statement ->
if (statement is IrDelegatingConstructorCall && !declaration.isPrimary
|| statement !is IrDelegatingConstructorCall && statement !is IrReturn) {
recordRegion(statement)
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
// override fun visitFieldAccess(expression: IrFieldAccessExpression) {
// expression.receiver?.let { recordRegion(it) }
@@ -117,48 +146,72 @@ private class IrFunctionRegionsCollector(
} else {
recordRegion(condition)
recordRegion(result, condition.endOffset, result.endOffset)
condition.acceptChildrenVoid(this)
condition.acceptVoid(this)
}
result.acceptChildrenVoid(this)
result.acceptVoid(this)
}
}
override fun visitLoop(loop: IrLoop) {
val condition = loop.condition
recordRegion(condition)
condition.acceptChildrenVoid(this)
condition.acceptVoid(this)
val body = loop.body ?: return
when (loop) {
is IrWhileLoop -> recordRegion(body, condition.endOffset, body.endOffset)
is IrDoWhileLoop -> recordRegion(body, body.startOffset, condition.startOffset)
}
body.acceptChildrenVoid(this)
body.acceptVoid(this)
}
override fun visitBlock(expression: IrBlock) {
when (expression) {
is IrReturnableBlock -> {
val file = (expression.sourceFileSymbol?.owner)
if (file != null && file != currentFile && fileFilter(file)) {
recordRegion(expression)
irFileStack.push(file)
expression.acceptChildrenVoid(this)
irFileStack.pop()
}
override fun visitReturn(expression: IrReturn) {
val next = irStatementsStack.lastOrNull()?.next ?: return
val nextRegion = recordRegion(next, expression.endOffset, currentRegion.endOffset) ?: return
currentRegion.endOffset = expression.endOffset
regionStack.pop()
regionStack.push(nextRegion)
}
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) {
recordRegion(irElement, irElement.startOffset, irElement.endOffset, kind)
}
private fun recordRegion(irElement: IrElement, kind: RegionKind = RegionKind.Code)
= Region.fromIr(irElement, currentFile, kind)?.also { regions[irElement] = it }
private fun recordRegion(irElement: IrElement, startOffset: Int, endOffset: Int, kind: RegionKind = RegionKind.Code) {
if (startOffset == UNDEFINED_OFFSET || endOffset == UNDEFINED_OFFSET) {
return
}
regions[irElement] = Region.fromOffset(startOffset, endOffset, currentFile, kind)
}
private fun recordRegion(irElement: IrElement, startOffset: Int, endOffset: Int, kind: RegionKind = RegionKind.Code)
= Region.fromOffset(startOffset, endOffset, currentFile, kind)?.also { regions[irElement] = it }
private fun IrElement.hasValidOffsets() = startOffset != UNDEFINED_OFFSET && endOffset != UNDEFINED_OFFSET
&& startOffset != endOffset
}
+15
View File
@@ -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) {
binaryName = "CoverageSmoke0"
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()
}