aboutsummaryrefslogtreecommitdiff
path: root/org.jacoco.core/src/org
diff options
context:
space:
mode:
authorEvgeny Mandrikov <Godin@users.noreply.github.com>2018-12-21 12:37:39 +0100
committerMarc R. Hoffmann <hoffmann@mountainminds.com>2018-12-21 12:37:39 +0100
commitd919b8e8c1341c2713094efd539ff4b5a54b3598 (patch)
treec95072b12620cc1502d91b2d8b58222cf6319671 /org.jacoco.core/src/org
parent9ad5e6ca90822fc0f99fc2c1600d41d38ec69e47 (diff)
downloadjacoco-d919b8e8c1341c2713094efd539ff4b5a54b3598.tar.gz
Add filter for Kotlin coroutines (#802)
Diffstat (limited to 'org.jacoco.core/src/org')
-rw-r--r--org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/AbstractMatcher.java11
-rw-r--r--org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/Filters.java3
-rw-r--r--org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinCoroutineFilter.java125
-rw-r--r--org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinUnsafeCastOperatorFilter.java2
-rw-r--r--org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinWhenFilter.java2
5 files changed, 134 insertions, 9 deletions
diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/AbstractMatcher.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/AbstractMatcher.java
index a4dd6208..2ae1499b 100644
--- a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/AbstractMatcher.java
+++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/AbstractMatcher.java
@@ -42,16 +42,15 @@ abstract class AbstractMatcher {
}
/**
- * Moves {@link #cursor} to next instruction if it is <code>NEW</code> with
- * given operand, otherwise sets it to <code>null</code>.
+ * Moves {@link #cursor} to next instruction if it is {@link TypeInsnNode}
+ * with given opcode and operand, otherwise sets it to <code>null</code>.
*/
- final void nextIsNew(final String desc) {
- nextIs(Opcodes.NEW);
+ final void nextIsType(final int opcode, final String desc) {
+ nextIs(opcode);
if (cursor == null) {
return;
}
- final TypeInsnNode i = (TypeInsnNode) cursor;
- if (desc.equals(i.desc)) {
+ if (((TypeInsnNode) cursor).desc.equals(desc)) {
return;
}
cursor = null;
diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/Filters.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/Filters.java
index f7ffa98d..a1116548 100644
--- a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/Filters.java
+++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/Filters.java
@@ -42,7 +42,8 @@ public final class Filters implements IFilter {
new KotlinLateinitFilter(), new KotlinWhenFilter(),
new KotlinWhenStringFilter(),
new KotlinUnsafeCastOperatorFilter(),
- new KotlinDefaultArgumentsFilter(), new KotlinInlineFilter());
+ new KotlinDefaultArgumentsFilter(), new KotlinInlineFilter(),
+ new KotlinCoroutineFilter());
}
private Filters(final IFilter... filters) {
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
new file mode 100644
index 00000000..f2ecd340
--- /dev/null
+++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinCoroutineFilter.java
@@ -0,0 +1,125 @@
+/*******************************************************************************
+ * 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 java.util.ArrayList;
+import java.util.List;
+
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.tree.AbstractInsnNode;
+import org.objectweb.asm.tree.LdcInsnNode;
+import org.objectweb.asm.tree.MethodNode;
+import org.objectweb.asm.tree.TableSwitchInsnNode;
+
+/**
+ * Filters branches that Kotlin compiler generates for coroutines.
+ */
+public final class KotlinCoroutineFilter implements IFilter {
+
+ public void filter(final MethodNode methodNode,
+ final IFilterContext context, final IFilterOutput output) {
+
+ if (!KotlinGeneratedFilter.isKotlinClass(context)) {
+ return;
+ }
+
+ if (!"invokeSuspend".equals(methodNode.name)) {
+ return;
+ }
+
+ new Matcher().match(methodNode, output);
+
+ }
+
+ private static class Matcher extends AbstractMatcher {
+ private void match(final MethodNode methodNode,
+ final IFilterOutput output) {
+ cursor = methodNode.instructions.getFirst();
+ nextIsInvokeStatic("kotlin/coroutines/intrinsics/IntrinsicsKt",
+ "getCOROUTINE_SUSPENDED");
+ nextIsVar(Opcodes.ASTORE, "COROUTINE_SUSPENDED");
+ nextIsVar(Opcodes.ALOAD, "this");
+ nextIs(Opcodes.GETFIELD);
+ nextIs(Opcodes.TABLESWITCH);
+ if (cursor == null) {
+ return;
+ }
+ final TableSwitchInsnNode s = (TableSwitchInsnNode) cursor;
+ final List<AbstractInsnNode> ignore = new ArrayList<AbstractInsnNode>(
+ s.labels.size() * 2);
+
+ nextIs(Opcodes.ALOAD);
+ nextIs(Opcodes.DUP);
+ nextIsType(Opcodes.INSTANCEOF, "kotlin/Result$Failure");
+ nextIs(Opcodes.IFEQ);
+ nextIsType(Opcodes.CHECKCAST, "kotlin/Result$Failure");
+ nextIs(Opcodes.GETFIELD);
+ nextIs(Opcodes.ATHROW);
+ nextIs(Opcodes.POP);
+
+ if (cursor == null) {
+ return;
+ }
+ ignore.add(s);
+ ignore.add(cursor);
+
+ for (AbstractInsnNode i = methodNode.instructions
+ .getFirst(); i != null; i = i.getNext()) {
+ cursor = i;
+ nextIsVar(Opcodes.ALOAD, "COROUTINE_SUSPENDED");
+ nextIs(Opcodes.IF_ACMPNE);
+ nextIsVar(Opcodes.ALOAD, "COROUTINE_SUSPENDED");
+ nextIs(Opcodes.ARETURN);
+
+ nextIs(Opcodes.ALOAD);
+ nextIs(Opcodes.DUP);
+ nextIsType(Opcodes.INSTANCEOF, "kotlin/Result$Failure");
+ nextIs(Opcodes.IFEQ);
+ nextIsType(Opcodes.CHECKCAST, "kotlin/Result$Failure");
+ nextIs(Opcodes.GETFIELD);
+ nextIs(Opcodes.ATHROW);
+ nextIs(Opcodes.POP);
+
+ nextIs(Opcodes.ALOAD);
+ if (cursor != null) {
+ ignore.add(i);
+ ignore.add(cursor);
+ }
+ }
+
+ if (ignore.size() != s.labels.size() * 2) {
+ return;
+ }
+
+ cursor = s.dflt;
+ nextIsType(Opcodes.NEW, "java/lang/IllegalStateException");
+ nextIs(Opcodes.DUP);
+ nextIs(Opcodes.LDC);
+ if (!((LdcInsnNode) cursor).cst.equals(
+ "call to 'resume' before 'invoke' with coroutine")) {
+ return;
+ }
+ nextIsInvokeSuper("java/lang/IllegalStateException",
+ "(Ljava/lang/String;)V");
+ nextIs(Opcodes.ATHROW);
+ if (cursor == null) {
+ return;
+ }
+
+ output.ignore(s.dflt, cursor);
+ for (int i = 0; i < ignore.size(); i += 2) {
+ output.ignore(ignore.get(i), ignore.get(i + 1));
+ }
+ }
+ }
+
+}
diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinUnsafeCastOperatorFilter.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinUnsafeCastOperatorFilter.java
index b65cac27..ed52aaf8 100644
--- a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinUnsafeCastOperatorFilter.java
+++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinUnsafeCastOperatorFilter.java
@@ -43,7 +43,7 @@ public final class KotlinUnsafeCastOperatorFilter implements IFilter {
}
cursor = start;
- nextIsNew(KOTLIN_TYPE_CAST_EXCEPTION);
+ nextIsType(Opcodes.NEW, KOTLIN_TYPE_CAST_EXCEPTION);
nextIs(Opcodes.DUP);
nextIs(Opcodes.LDC);
if (cursor == null) {
diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinWhenFilter.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinWhenFilter.java
index a39356f9..e9ffe045 100644
--- a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinWhenFilter.java
+++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinWhenFilter.java
@@ -50,7 +50,7 @@ public final class KotlinWhenFilter implements IFilter {
}
cursor = start;
- nextIsNew(EXCEPTION);
+ nextIsType(Opcodes.NEW, EXCEPTION);
nextIs(Opcodes.DUP);
nextIsInvokeSuper(EXCEPTION, "()V");
nextIs(Opcodes.ATHROW);