From d919b8e8c1341c2713094efd539ff4b5a54b3598 Mon Sep 17 00:00:00 2001 From: Evgeny Mandrikov Date: Fri, 21 Dec 2018 12:37:39 +0100 Subject: Add filter for Kotlin coroutines (#802) --- .../analysis/filter/AbstractMatcherTest.java | 13 +- .../analysis/filter/KotlinCoroutineFilterTest.java | 135 +++++++++++++++++++++ 2 files changed, 144 insertions(+), 4 deletions(-) create mode 100644 org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/KotlinCoroutineFilterTest.java (limited to 'org.jacoco.core.test') diff --git a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/AbstractMatcherTest.java b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/AbstractMatcherTest.java index 69c520ff..e89bd419 100644 --- a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/AbstractMatcherTest.java +++ b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/AbstractMatcherTest.java @@ -223,23 +223,28 @@ public class AbstractMatcherTest { } @Test - public void nextIsNew() { + public void nextIsType() { m.visitInsn(Opcodes.NOP); m.visitTypeInsn(Opcodes.NEW, "descriptor"); + // should set cursor to null when opcode mismatch + matcher.cursor = m.instructions.getFirst(); + matcher.nextIsType(Opcodes.CHECKCAST, "descriptor"); + assertNull(matcher.cursor); + // should set cursor to null when descriptor mismatch matcher.cursor = m.instructions.getFirst(); - matcher.nextIsNew("another_descriptor"); + matcher.nextIsType(Opcodes.NEW, "another_descriptor"); assertNull(matcher.cursor); // should set cursor to next instruction when match matcher.cursor = m.instructions.getFirst(); - matcher.nextIsNew("descriptor"); + matcher.nextIsType(Opcodes.NEW, "descriptor"); assertSame(m.instructions.getLast(), matcher.cursor); // should not do anything when cursor is null matcher.cursor = null; - matcher.nextIsNew("descriptor"); + matcher.nextIsType(Opcodes.NEW, "descriptor"); } @Test 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 new file mode 100644 index 00000000..13cdc397 --- /dev/null +++ b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/KotlinCoroutineFilterTest.java @@ -0,0 +1,135 @@ +/******************************************************************************* + * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Evgeny Mandrikov - 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.MethodNode; + +/** + * Unit test for {@link KotlinCoroutineFilter}. + */ +public class KotlinCoroutineFilterTest extends FilterTestBase { + + private final IFilter filter = new KotlinCoroutineFilter(); + + private final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0, + "invokeSuspend", "(Ljava/lang/Object;)Ljava/lang/Object;", null, + null); + + /** + *
+	 *     runBlocking {
+	 *         nop()
+	 *         suspendingFunction()
+	 *         nop()
+	 *     }
+	 * 
+ */ + @Test + public void should_filter() { + context.classAnnotations + .add(KotlinGeneratedFilter.KOTLIN_METADATA_DESC); + + m.visitLabel(new Label()); + m.visitMethodInsn(Opcodes.INVOKESTATIC, + "kotlin/coroutines/intrinsics/IntrinsicsKt", + "getCOROUTINE_SUSPENDED", "()Ljava/lang/Object;", false); + m.visitVarInsn(Opcodes.ASTORE, 3); + + m.visitVarInsn(Opcodes.ALOAD, 0); + // line of "runBlocking" + m.visitFieldInsn(Opcodes.GETFIELD, "", "label", "I"); + final Label dflt = new Label(); + final Label state0 = new Label(); + final Label state1 = new Label(); + m.visitTableSwitchInsn(0, 1, dflt, state0, state1); + final Range range1 = new Range(); + range1.fromInclusive = m.instructions.getLast(); + + m.visitLabel(state0); + + { + m.visitVarInsn(Opcodes.ALOAD, 1); + m.visitInsn(Opcodes.DUP); + m.visitTypeInsn(Opcodes.INSTANCEOF, "kotlin/Result$Failure"); + Label label = new Label(); + m.visitJumpInsn(Opcodes.IFEQ, label); + m.visitTypeInsn(Opcodes.CHECKCAST, "kotlin/Result$Failure"); + m.visitFieldInsn(Opcodes.GETFIELD, "kotlin/Result$Failure", + "exception", "Ljava/lang/Throwable"); + m.visitInsn(Opcodes.ATHROW); + m.visitInsn(Opcodes.POP); + range1.toInclusive = m.instructions.getLast(); + m.visitLabel(label); + } + + // line before "suspendingFunction" + m.visitInsn(Opcodes.NOP); + + // line of "suspendingFunction" + m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "", "suspendingFunction", + "(Lkotlin/coroutines/Continuation;)Ljava/lang/Object;", false); + + m.visitInsn(Opcodes.DUP); + final Range range2 = new Range(); + range2.fromInclusive = m.instructions.getLast(); + m.visitVarInsn(Opcodes.ALOAD, 3); + final Label continuationLabelAfterLoadedResult = new Label(); + m.visitJumpInsn(Opcodes.IF_ACMPNE, continuationLabelAfterLoadedResult); + // line of "runBlocking" + m.visitVarInsn(Opcodes.ALOAD, 3); + m.visitInsn(Opcodes.ARETURN); + + m.visitLabel(state1); + + { + m.visitVarInsn(Opcodes.ALOAD, 1); + m.visitInsn(Opcodes.DUP); + m.visitTypeInsn(Opcodes.INSTANCEOF, "kotlin/Result$Failure"); + final Label label = new Label(); + m.visitJumpInsn(Opcodes.IFEQ, label); + m.visitTypeInsn(Opcodes.CHECKCAST, "kotlin/Result$Failure"); + m.visitFieldInsn(Opcodes.GETFIELD, "kotlin/Result$Failure", + "exception", "Ljava/lang/Throwable"); + m.visitInsn(Opcodes.ATHROW); + m.visitInsn(Opcodes.POP); + m.visitLabel(label); + } + m.visitVarInsn(Opcodes.ALOAD, 1); + range2.toInclusive = m.instructions.getLast(); + m.visitLabel(continuationLabelAfterLoadedResult); + + // line after "suspendingFunction" + m.visitInsn(Opcodes.NOP); + m.visitInsn(Opcodes.ARETURN); + + m.visitLabel(dflt); + final Range range0 = new Range(); + range0.fromInclusive = m.instructions.getLast(); + m.visitTypeInsn(Opcodes.NEW, "java/lang/IllegalStateException"); + m.visitInsn(Opcodes.DUP); + m.visitLdcInsn("call to 'resume' before 'invoke' with coroutine"); + m.visitMethodInsn(Opcodes.INVOKESPECIAL, + "java/lang/IllegalStateException", "", + "(Ljava/lang/String;)V", false); + m.visitInsn(Opcodes.ATHROW); + range0.toInclusive = m.instructions.getLast(); + + filter.filter(m, context, output); + + assertIgnored(range0, range1, range2); + } + +} -- cgit v1.2.3