Skip to content

Commit

Permalink
Update filter for suspending functions with tail call optimization (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
Godin committed Feb 9, 2020
1 parent 04fe200 commit 71f9341
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 2 deletions.
2 changes: 1 addition & 1 deletion org.jacoco.core.test.validation.kotlin/pom.xml
Expand Up @@ -26,7 +26,7 @@

<properties>
<bytecode.version>6</bytecode.version>
<kotlin.version>1.3.31</kotlin.version>
<kotlin.version>1.3.61</kotlin.version>
</properties>

<dependencies>
Expand Down
Expand Up @@ -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()
}
Expand All @@ -37,6 +42,7 @@ object KotlinCoroutineTarget {
nop(x) // assertFullyCovered()
suspendingFunction() // assertFullyCovered()
nop(x) // assertFullyCovered()
suspendingFunctionWithTailCallOptimization()
} // assertFullyCovered()

}
Expand Down
Expand Up @@ -374,4 +374,80 @@ public void should_filter_suspending_functions() {
assertIgnored(range0, range1, range2);
}

/**
* <pre>
* suspend fun example(b: Boolean) {
* if (b)
* suspendingFunction()
* else
* suspendingFunction()
* }
* </pre>
*/
@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);
}

}
Expand Up @@ -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();
Expand Down
3 changes: 3 additions & 0 deletions org.jacoco.doc/docroot/doc/changes.html
Expand Up @@ -32,6 +32,9 @@ <h3>New Features</h3>
<li>Methods generated by Kotlin compiler for non-overridden non-abstract methods
of interfaces are filtered out during generation of report
(GitHub <a href="https://github.com/jacoco/jacoco/issues/1012">#1012</a>).</li>
<li>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 <a href="https://github.com/jacoco/jacoco/issues/1016">#1016</a>).</li>
</ul>

<h3>Non-functional Changes</h3>
Expand Down

0 comments on commit 71f9341

Please sign in to comment.