Skip to content
This repository was archived by the owner on Jan 13, 2025. It is now read-only.

For loop filter #3

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -72,18 +72,38 @@ object KotlinControlStructuresTarget {

private fun missedForBlock() {

for (j in i2()..i1()) { // assertPartlyCovered(3, 1)
for (j in i2()..i1()) { // assertPartlyCovered()
nop() // assertNotCovered()
}

}

private fun executedForBlock() {

for (j in i1()..i2()) { // assertFullyCovered(1, 3)
for (j in i1()..i2()) { // assertFullyCovered()
nop() // assertFullyCovered()
}

val limit = 10 // assertFullyCovered()
for (j in i1() until limit) { // assertFullyCovered()
nop() // assertFullyCovered()
}

for (j in limit downTo i1()) { // assertFullyCovered()
nop() // assertFullyCovered()
}

for (i in 0 until i1()) { // assertFullyCovered()
nop() // assertFullyCovered()
}

for (i in 0 until i2()) { // assertFullyCovered()
nop() // assertFullyCovered()
}

for (i in i1() downTo 0) { // assertFullyCovered()
nop() // assertFullyCovered()
}
}

private fun missedForEachBlock() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
/*******************************************************************************
* Copyright (c) 2009, 2023 Mountainminds GmbH & Co. KG and Contributors
* This program and the accompanying materials are made available under
* the terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Fabian Mastenbroek - initial API and implementation
*
*******************************************************************************/
package org.jacoco.core.internal.analysis.filter;

import org.jacoco.core.internal.instr.InstrSupport;
import org.junit.Test;
import org.objectweb.asm.Label;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.MethodNode;

/**
* Unit tests for {@link KotlinForLoopFilter}.
*/
public class KotlinForLoopFilterTest extends FilterTestBase {

private final KotlinForLoopFilter filter = new KotlinForLoopFilter();

/**
* <pre>
* class Example {
* fun example() {
* for (j in 0 until i1()) {}
* }
* private fun i1() = 1
* }
* </pre>
*/
@Test
public void should_filter_Kotlin_1_5_until() {
final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0,
"example", "()V", null, null);
final Label label1 = new Label();
final Label label2 = new Label();

m.visitInsn(Opcodes.ICONST_0);
m.visitVarInsn(Opcodes.ISTORE, 0);
m.visitMethodInsn(Opcodes.INVOKESTATIC, "ExampleKt", "i1", "()I",
false);
m.visitVarInsn(Opcodes.ISTORE, 1);
m.visitVarInsn(Opcodes.ILOAD, 0);
m.visitVarInsn(Opcodes.ILOAD, 1);
m.visitJumpInsn(Opcodes.IF_ICMPGE, label1);
final AbstractInsnNode ignored1 = m.instructions.getLast();
m.visitLabel(label2);
m.visitVarInsn(Opcodes.ILOAD, 0);
m.visitVarInsn(Opcodes.ISTORE, 2);
m.visitInsn(Opcodes.IINC);
m.visitVarInsn(Opcodes.ILOAD, 0);
m.visitVarInsn(Opcodes.ILOAD, 1);
m.visitJumpInsn(Opcodes.IF_ICMPLT, label2);
final AbstractInsnNode ignored2 = m.instructions.getLast();
m.visitLabel(label1);
m.visitInsn(Opcodes.RETURN);

filter.filter(m, context, output);
assertIgnored(new Range(ignored1, ignored1),
new Range(ignored2, ignored2));
}

/**
* <pre>
* class Example {
* fun example() {
* for (i in i1() downTo 0) {}
* }
* private fun i1() = 1
* }
* </pre>
*/
@Test
public void should_filter_Kotlin_1_5_downTo() {
final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0,
"example", "()V", null, null);
final Label label1 = new Label();
final Label label2 = new Label();

m.visitMethodInsn(Opcodes.INVOKESTATIC, "ExampleKt", "i1", "()I",
false);
m.visitVarInsn(Opcodes.ISTORE, 0);
m.visitInsn(Opcodes.ICONST_0);
m.visitVarInsn(Opcodes.ISTORE, 1);
m.visitVarInsn(Opcodes.ILOAD, 1);
m.visitJumpInsn(Opcodes.IF_ICMPGT, label1);
final AbstractInsnNode ignored1 = m.instructions.getLast();
m.visitLabel(label2);
m.visitVarInsn(Opcodes.ILOAD, 0);
m.visitVarInsn(Opcodes.ISTORE, 1);
m.visitInsn(Opcodes.IINC);
m.visitInsn(Opcodes.ICONST_0);
m.visitVarInsn(Opcodes.ILOAD, 1);
m.visitJumpInsn(Opcodes.IF_ICMPLE, label2);
final AbstractInsnNode ignored2 = m.instructions.getLast();
m.visitLabel(label1);
m.visitInsn(Opcodes.RETURN);

filter.filter(m, context, output);
assertIgnored(new Range(ignored1, ignored1),
new Range(ignored2, ignored2));
}

/**
* <pre>
* class Example {
* fun example() {
* val limit = 10
* for (j in limit downTo i1()) {}
* }
* private fun i1() = 1
* }
* </pre>
*/
@Test
public void should_filter_Kotlin_1_5_downTo_val() {
final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0,
"example", "()V", null, null);
final Label label1 = new Label();
final Label label2 = new Label();

m.visitVarInsn(Opcodes.BIPUSH, 10);
m.visitVarInsn(Opcodes.ISTORE, 0);
m.visitVarInsn(Opcodes.ILOAD, 0);
m.visitVarInsn(Opcodes.ISTORE, 1);
m.visitMethodInsn(Opcodes.INVOKESTATIC, "ExampleKt", "i1", "()I",
false);
m.visitVarInsn(Opcodes.ISTORE, 2);
m.visitVarInsn(Opcodes.ILOAD, 2);
m.visitVarInsn(Opcodes.ILOAD, 1);
m.visitJumpInsn(Opcodes.IF_ICMPGT, label1);
final AbstractInsnNode ignored1 = m.instructions.getLast();
m.visitLabel(label2);
m.visitVarInsn(Opcodes.ILOAD, 1);
m.visitVarInsn(Opcodes.ISTORE, 3);
m.visitInsn(Opcodes.IINC);
m.visitVarInsn(Opcodes.ILOAD, 3);
m.visitVarInsn(Opcodes.ILOAD, 2);
m.visitJumpInsn(Opcodes.IF_ICMPNE, label2);
final AbstractInsnNode ignored2 = m.instructions.getLast();
m.visitLabel(label1);
m.visitInsn(Opcodes.RETURN);

filter.filter(m, context, output);
assertIgnored(new Range(ignored1, ignored1),
new Range(ignored2, ignored2));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public static IFilter all() {
new RecordPatternFilter(), //
new AnnotationGeneratedFilter(), new KotlinGeneratedFilter(),
new KotlinLateinitFilter(), new KotlinWhenFilter(),
new KotlinWhenStringFilter(),
new KotlinWhenStringFilter(), new KotlinForLoopFilter(),
new KotlinUnsafeCastOperatorFilter(),
new KotlinNotNullOperatorFilter(),
new KotlinDefaultArgumentsFilter(), new KotlinInlineFilter(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*******************************************************************************
* Copyright (c) 2009, 2023 Mountainminds GmbH & Co. KG and Contributors
* This program and the accompanying materials are made available under
* the terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Fabian Mastenbroek - initial API and implementation
*
*******************************************************************************/
package org.jacoco.core.internal.analysis.filter;

import org.objectweb.asm.Label;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.JumpInsnNode;
import org.objectweb.asm.tree.LabelNode;
import org.objectweb.asm.tree.MethodNode;

/**
* Filters branches in bytecode that the Kotlin compiler generates for
* <code>for</code> loops as they are not coverable most of the time.
*/
public class KotlinForLoopFilter implements IFilter {

public void filter(final MethodNode methodNode,
final IFilterContext context, final IFilterOutput output) {
final Matcher matcher = new Matcher();
for (final AbstractInsnNode node : methodNode.instructions) {
matcher.match(node, output);
}
}

private static class Matcher extends AbstractMatcher {

public void match(final AbstractInsnNode start, IFilterOutput output) {
if (start.getOpcode() != Opcodes.IF_ICMPGE
&& start.getOpcode() != Opcodes.IF_ICMPGT) {
return;
}
cursor = start;
AbstractInsnNode loopLabelNode = start.getNext();
if (loopLabelNode instanceof LabelNode) {
Label loopLabel = ((LabelNode) loopLabelNode).getLabel();
LabelNode jumpTarget = ((JumpInsnNode) cursor).label;
if (isLoop(jumpTarget, loopLabel)) {
output.ignore(start, start);
output.ignore(jumpTarget.getPrevious(),
jumpTarget.getPrevious());
}
}
}

private boolean isLoop(LabelNode jumpTarget, Label loopLabel) {
nextIs(Opcodes.ILOAD);
nextIs(Opcodes.ISTORE);
nextIs(Opcodes.IINC);
// follow the jump node
for (AbstractInsnNode j = cursor; j != null; j = j.getNext()) {
if (j == jumpTarget) {
// if the label prior to the jump target matches
// we can be sure that this is the loop we are looking for
AbstractInsnNode previousOpcode = j.getPrevious();
return ((JumpInsnNode) previousOpcode).label.getLabel()
.equals(loopLabel);
}
}
return false;
}
}
}