diff --git a/org.jacoco.core.test.validation.kotlin/src/org/jacoco/core/test/validation/kotlin/targets/KotlinCoroutineTarget.kt b/org.jacoco.core.test.validation.kotlin/src/org/jacoco/core/test/validation/kotlin/targets/KotlinCoroutineTarget.kt
index 784e488d65..bef0cebae0 100644
--- a/org.jacoco.core.test.validation.kotlin/src/org/jacoco/core/test/validation/kotlin/targets/KotlinCoroutineTarget.kt
+++ b/org.jacoco.core.test.validation.kotlin/src/org/jacoco/core/test/validation/kotlin/targets/KotlinCoroutineTarget.kt
@@ -25,6 +25,11 @@ object KotlinCoroutineTarget {
nop() // assertFullyCovered()
} // assertFullyCovered()
+ private suspend fun suspendingFunctionWithTailCallOptimization() { // assertEmpty()
+ nop() // assertFullyCovered()
+ anotherSuspendingFunction() // assertFullyCovered()
+ } // assertFullyCovered()
+
private suspend fun anotherSuspendingFunction() {
nop() // assertFullyCovered()
}
@@ -37,6 +42,7 @@ object KotlinCoroutineTarget {
nop(x) // assertFullyCovered()
suspendingFunction() // assertFullyCovered()
nop(x) // assertFullyCovered()
+ suspendingFunctionWithTailCallOptimization()
} // assertFullyCovered()
}
diff --git a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/KotlinCoroutineFilterTest.java b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/KotlinCoroutineFilterTest.java
index c8ba9eb386..4f471c3c3b 100644
--- a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/KotlinCoroutineFilterTest.java
+++ b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/KotlinCoroutineFilterTest.java
@@ -374,4 +374,80 @@ public void should_filter_suspending_functions() {
assertIgnored(range0, range1, range2);
}
+ /**
+ *
+ * suspend fun example(b: Boolean) {
+ * if (b)
+ * suspendingFunction()
+ * else
+ * suspendingFunction()
+ * }
+ *
+ */
+ @Test
+ public void should_filter_suspending_functions_with_tail_call_optimization() {
+ final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0,
+ "example",
+ "(ZLkotlin/coroutines/Continuation;)Ljava/lang/Object;", null,
+ null);
+ context.classAnnotations
+ .add(KotlinGeneratedFilter.KOTLIN_METADATA_DESC);
+
+ final Label exit = new Label();
+
+ m.visitVarInsn(Opcodes.ILOAD, 1);
+ final Label next = new Label();
+ m.visitJumpInsn(Opcodes.IFEQ, next);
+
+ m.visitVarInsn(Opcodes.ALOAD, 0);
+ m.visitVarInsn(Opcodes.ALOAD, 2);
+ m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "", "suspendingFunction",
+ "(Lkotlin/coroutines/Continuation;)Ljava/lang/Object;", false);
+ final Range range1 = new Range();
+ {
+ m.visitInsn(Opcodes.DUP);
+ range1.fromInclusive = m.instructions.getLast();
+ m.visitMethodInsn(Opcodes.INVOKESTATIC,
+ "kotlin/coroutines/intrinsics/IntrinsicsKt",
+ "getCOROUTINE_SUSPENDED", "()Ljava/lang/Object;", false);
+ final Label label = new Label();
+ m.visitJumpInsn(Opcodes.IF_ACMPNE, label);
+ m.visitInsn(Opcodes.ARETURN);
+ m.visitLabel(label);
+ m.visitInsn(Opcodes.POP);
+ range1.toInclusive = m.instructions.getLast();
+ }
+
+ m.visitJumpInsn(Opcodes.GOTO, exit);
+ m.visitLabel(next);
+
+ m.visitVarInsn(Opcodes.ALOAD, 0);
+ m.visitVarInsn(Opcodes.ALOAD, 2);
+ m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "", "suspendingFunction",
+ "(Lkotlin/coroutines/Continuation;)Ljava/lang/Object;", false);
+ final Range range2 = new Range();
+ {
+ m.visitInsn(Opcodes.DUP);
+ range2.fromInclusive = m.instructions.getLast();
+ m.visitMethodInsn(Opcodes.INVOKESTATIC,
+ "kotlin/coroutines/intrinsics/IntrinsicsKt",
+ "getCOROUTINE_SUSPENDED", "()Ljava/lang/Object;", false);
+ final Label label = new Label();
+ m.visitJumpInsn(Opcodes.IF_ACMPNE, label);
+ m.visitInsn(Opcodes.ARETURN);
+ m.visitLabel(label);
+ m.visitInsn(Opcodes.POP);
+ range2.toInclusive = m.instructions.getLast();
+ }
+
+ m.visitLabel(exit);
+ m.visitFieldInsn(Opcodes.GETSTATIC, "kotlin/Unit", "INSTANCE",
+ "Lkotlin/Unit;");
+ m.visitInsn(Opcodes.ARETURN);
+
+ filter.filter(m, context, output);
+
+ assertIgnored(range1, range2);
+ }
+
}
diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinCoroutineFilter.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinCoroutineFilter.java
index 814f59f6eb..35ac6665ba 100644
--- a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinCoroutineFilter.java
+++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinCoroutineFilter.java
@@ -43,10 +43,28 @@ public void filter(final MethodNode methodNode,
}
new Matcher().match(methodNode, output);
-
+ new Matcher().matchOptimizedTailCall(methodNode, output);
}
private static class Matcher extends AbstractMatcher {
+
+ private void matchOptimizedTailCall(final MethodNode methodNode,
+ final IFilterOutput output) {
+ for (final AbstractInsnNode i : methodNode.instructions) {
+ cursor = i;
+ nextIs(Opcodes.DUP);
+ nextIsInvoke(Opcodes.INVOKESTATIC,
+ "kotlin/coroutines/intrinsics/IntrinsicsKt",
+ "getCOROUTINE_SUSPENDED", "()Ljava/lang/Object;");
+ nextIs(Opcodes.IF_ACMPNE);
+ nextIs(Opcodes.ARETURN);
+ nextIs(Opcodes.POP);
+ if (cursor != null) {
+ output.ignore(i.getNext(), cursor);
+ }
+ }
+ }
+
private void match(final MethodNode methodNode,
final IFilterOutput output) {
cursor = methodNode.instructions.getFirst();
diff --git a/org.jacoco.doc/docroot/doc/changes.html b/org.jacoco.doc/docroot/doc/changes.html
index 5f4ed69e8a..e466f1c13f 100644
--- a/org.jacoco.doc/docroot/doc/changes.html
+++ b/org.jacoco.doc/docroot/doc/changes.html
@@ -32,6 +32,9 @@ New Features
Methods generated by Kotlin compiler for non-overridden non-abstract methods
of interfaces are filtered out during generation of report
(GitHub #1012).
+ Branches added by the Kotlin compiler version 1.3.60 for suspending functions
+ with tail call optimization are filtered out during generation of report
+ (GitHub #1016).
Non-functional Changes