From 71f9341e088bb3347ecfea2f114e5e40bdc4326b Mon Sep 17 00:00:00 2001 From: Evgeny Mandrikov <138671+Godin@users.noreply.github.com> Date: Sun, 9 Feb 2020 20:14:24 +0200 Subject: [PATCH] Update filter for suspending functions with tail call optimization (#1016) --- .../pom.xml | 2 +- .../kotlin/targets/KotlinCoroutineTarget.kt | 6 ++ .../filter/KotlinCoroutineFilterTest.java | 76 +++++++++++++++++++ .../filter/KotlinCoroutineFilter.java | 20 ++++- org.jacoco.doc/docroot/doc/changes.html | 3 + 5 files changed, 105 insertions(+), 2 deletions(-) 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 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