From 19e5e87d31f594a45b50d38e8a91a8d63be2f7e2 Mon Sep 17 00:00:00 2001 From: Evgeny Mandrikov Date: Sat, 8 Feb 2020 11:30:15 +0100 Subject: [PATCH 1/6] (WIP) add validation test --- .../test/validation/kotlin/targets/KotlinCoroutineTarget.kt | 6 ++++++ 1 file changed, 6 insertions(+) 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() } From ad17003938adfe3ddd0fd6f5e7b8bb0209e63331 Mon Sep 17 00:00:00 2001 From: Evgeny Mandrikov Date: Sat, 8 Feb 2020 12:21:53 +0100 Subject: [PATCH 2/6] (WIP) upgrade Kotlin to 1.3.61 --- org.jacoco.core.test.validation.kotlin/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.jacoco.core.test.validation.kotlin/pom.xml b/org.jacoco.core.test.validation.kotlin/pom.xml index 635c8642f2..468b6472c9 100644 --- a/org.jacoco.core.test.validation.kotlin/pom.xml +++ b/org.jacoco.core.test.validation.kotlin/pom.xml @@ -26,7 +26,7 @@ 6 - 1.3.31 + 1.3.61 From beb2537fbd981ee2ee83645e6d0b69fd72241bcb Mon Sep 17 00:00:00 2001 From: Evgeny Mandrikov Date: Sat, 8 Feb 2020 12:31:26 +0100 Subject: [PATCH 3/6] (WIP) add unit test --- .../filter/KotlinCoroutineFilterTest.java | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) 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); + } + } From c4437a735a2711050200b292bbcfd6b2b018f6fd Mon Sep 17 00:00:00 2001 From: Evgeny Mandrikov Date: Sun, 9 Feb 2020 03:11:24 +0100 Subject: [PATCH 4/6] (WIP) implement --- .../filter/KotlinCoroutineFilter.java | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) 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..593ded14c3 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,29 @@ 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 (AbstractInsnNode i = methodNode.instructions + .getFirst(); i != null; i = i.getNext()) { + 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(); From 398dbb59bd77511419d50779292857d04bb358fb Mon Sep 17 00:00:00 2001 From: Evgeny Mandrikov Date: Sun, 9 Feb 2020 03:20:49 +0100 Subject: [PATCH 5/6] (WIP) update changelog --- org.jacoco.doc/docroot/doc/changes.html | 3 +++ 1 file changed, 3 insertions(+) diff --git a/org.jacoco.doc/docroot/doc/changes.html b/org.jacoco.doc/docroot/doc/changes.html index da3817fc35..8bd4e329c2 100644 --- a/org.jacoco.doc/docroot/doc/changes.html +++ b/org.jacoco.doc/docroot/doc/changes.html @@ -29,6 +29,9 @@

New Features

(GitHub #990).
  • Bridge methods are filtered out during generation of report (GitHub #1010).
  • +
  • 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

    From f3b7b06e1baef0e90d1317d0404f156d6c87ed92 Mon Sep 17 00:00:00 2001 From: Evgeny Mandrikov Date: Sun, 9 Feb 2020 03:35:38 +0100 Subject: [PATCH 6/6] (WIP) cleanup: use enhanced for-loop --- .../core/internal/analysis/filter/KotlinCoroutineFilter.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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 593ded14c3..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 @@ -50,8 +50,7 @@ private static class Matcher extends AbstractMatcher { private void matchOptimizedTailCall(final MethodNode methodNode, final IFilterOutput output) { - for (AbstractInsnNode i = methodNode.instructions - .getFirst(); i != null; i = i.getNext()) { + for (final AbstractInsnNode i : methodNode.instructions) { cursor = i; nextIs(Opcodes.DUP); nextIsInvoke(Opcodes.INVOKESTATIC,