diff options
author | Evgeny Mandrikov <Godin@users.noreply.github.com> | 2018-11-27 13:16:14 +0100 |
---|---|---|
committer | Marc R. Hoffmann <hoffmann@mountainminds.com> | 2018-11-27 13:16:14 +0100 |
commit | 78b28dc8463c301b207c60f0394251d4f2ae9aba (patch) | |
tree | e7b3a4179c10e834ec4bc43d972cc8c5d82b5bef /org.jacoco.core | |
parent | 276e1cb2ad4b4e2341e487a90285382324eec712 (diff) | |
download | jacoco-78b28dc8463c301b207c60f0394251d4f2ae9aba.tar.gz |
Report code coverage correctly for Kotlin methods with default arguments (#774)
Diffstat (limited to 'org.jacoco.core')
5 files changed, 124 insertions, 7 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 9b01e777..a4dd6208 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 @@ -154,7 +154,11 @@ abstract class AbstractMatcher { skipNonOpcodes(); } - private void skipNonOpcodes() { + /** + * Moves {@link #cursor} through {@link AbstractInsnNode#FRAME}, + * {@link AbstractInsnNode#LABEL}, {@link AbstractInsnNode#LINE}. + */ + final void skipNonOpcodes() { while (cursor != null && (cursor.getType() == AbstractInsnNode.FRAME || cursor.getType() == AbstractInsnNode.LABEL || cursor.getType() == AbstractInsnNode.LINE)) { 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 f6a7a1df..76b050f4 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 @@ -35,7 +35,8 @@ public final class Filters implements IFilter { new EnumEmptyConstructorFilter(), new AnnotationGeneratedFilter(), new KotlinGeneratedFilter(), new KotlinLateinitFilter(), new KotlinWhenFilter(), new KotlinWhenStringFilter(), - new KotlinUnsafeCastOperatorFilter()); + new KotlinUnsafeCastOperatorFilter(), + new KotlinDefaultArgumentsFilter()); private final IFilter[] filters; diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinDefaultArgumentsFilter.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinDefaultArgumentsFilter.java new file mode 100644 index 00000000..8c2bdeba --- /dev/null +++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinDefaultArgumentsFilter.java @@ -0,0 +1,97 @@ +/******************************************************************************* + * 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.HashSet; +import java.util.Set; + +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.JumpInsnNode; +import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.tree.VarInsnNode; + +/** + * Filters branches that Kotlin compiler generates for default arguments. + * + * For each default argument Kotlin compiler generates following bytecode to + * determine if it should be used or not: + * + * <pre> + * ILOAD maskVar + * ICONST_x, BIPUSH, SIPUSH, LDC or LDC_W + * IAND + * IFEQ label + * default argument + * label: + * </pre> + * + * Where <code>maskVar</code> is penultimate argument of synthetic method with + * suffix "$default". And its value can't be zero - invocation with all + * arguments uses original non synthetic method, thus <code>IFEQ</code> + * instructions should be ignored. + */ +public final class KotlinDefaultArgumentsFilter implements IFilter { + + static boolean isDefaultArgumentsMethodName(final String methodName) { + return methodName.endsWith("$default"); + } + + public void filter(final MethodNode methodNode, + final IFilterContext context, final IFilterOutput output) { + if ((methodNode.access & Opcodes.ACC_SYNTHETIC) == 0) { + return; + } + if (!isDefaultArgumentsMethodName(methodNode.name)) { + return; + } + if (!KotlinGeneratedFilter.isKotlinClass(context)) { + return; + } + + new Matcher().match(methodNode, output); + } + + private static class Matcher extends AbstractMatcher { + public void match(final MethodNode methodNode, + final IFilterOutput output) { + cursor = methodNode.instructions.getFirst(); + + final Set<AbstractInsnNode> ignore = new HashSet<AbstractInsnNode>(); + final int maskVar = Type.getMethodType(methodNode.desc) + .getArgumentTypes().length - 2; + while (true) { + if (cursor.getOpcode() != Opcodes.ILOAD) { + break; + } + if (((VarInsnNode) cursor).var != maskVar) { + break; + } + next(); + nextIs(Opcodes.IAND); + nextIs(Opcodes.IFEQ); + if (cursor == null) { + return; + } + ignore.add(cursor); + cursor = ((JumpInsnNode) cursor).label; + skipNonOpcodes(); + } + + for (AbstractInsnNode i : ignore) { + output.ignore(i, i); + } + } + } + +} diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinGeneratedFilter.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinGeneratedFilter.java index 68331492..722fd716 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinGeneratedFilter.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinGeneratedFilter.java @@ -23,6 +23,10 @@ public class KotlinGeneratedFilter implements IFilter { static final String KOTLIN_METADATA_DESC = "Lkotlin/Metadata;"; + static boolean isKotlinClass(final IFilterContext context) { + return context.getClassAnnotations().contains(KOTLIN_METADATA_DESC); + } + public void filter(final MethodNode methodNode, final IFilterContext context, final IFilterOutput output) { @@ -32,7 +36,7 @@ public class KotlinGeneratedFilter implements IFilter { return; } - if (!context.getClassAnnotations().contains(KOTLIN_METADATA_DESC)) { + if (!isKotlinClass(context)) { return; } diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/SyntheticFilter.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/SyntheticFilter.java index 52d38bd2..5dfeecf5 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/SyntheticFilter.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/SyntheticFilter.java @@ -21,11 +21,22 @@ public final class SyntheticFilter implements IFilter { public void filter(final MethodNode methodNode, final IFilterContext context, final IFilterOutput output) { - if ((methodNode.access & Opcodes.ACC_SYNTHETIC) != 0 - && !methodNode.name.startsWith("lambda$")) { - output.ignore(methodNode.instructions.getFirst(), - methodNode.instructions.getLast()); + if ((methodNode.access & Opcodes.ACC_SYNTHETIC) == 0) { + return; } + + if (methodNode.name.startsWith("lambda$")) { + return; + } + + if (KotlinDefaultArgumentsFilter + .isDefaultArgumentsMethodName(methodNode.name) + && KotlinGeneratedFilter.isKotlinClass(context)) { + return; + } + + output.ignore(methodNode.instructions.getFirst(), + methodNode.instructions.getLast()); } } |