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) --- .../internal/analysis/filter/AbstractMatcher.java | 11 +- .../core/internal/analysis/filter/Filters.java | 3 +- .../analysis/filter/KotlinCoroutineFilter.java | 125 +++++++++++++++++++++ .../filter/KotlinUnsafeCastOperatorFilter.java | 2 +- .../internal/analysis/filter/KotlinWhenFilter.java | 2 +- 5 files changed, 134 insertions(+), 9 deletions(-) create mode 100644 org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinCoroutineFilter.java (limited to 'org.jacoco.core/src/org') 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 NEW with - * given operand, otherwise sets it to null. + * Moves {@link #cursor} to next instruction if it is {@link TypeInsnNode} + * with given opcode and operand, otherwise sets it to null. */ - 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 ignore = new ArrayList( + 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); -- cgit v1.2.3