aboutsummaryrefslogtreecommitdiff
path: root/org.jacoco.core
diff options
context:
space:
mode:
authorEvgeny Mandrikov <Godin@users.noreply.github.com>2018-11-27 13:16:14 +0100
committerMarc R. Hoffmann <hoffmann@mountainminds.com>2018-11-27 13:16:14 +0100
commit78b28dc8463c301b207c60f0394251d4f2ae9aba (patch)
treee7b3a4179c10e834ec4bc43d972cc8c5d82b5bef /org.jacoco.core
parent276e1cb2ad4b4e2341e487a90285382324eec712 (diff)
downloadjacoco-78b28dc8463c301b207c60f0394251d4f2ae9aba.tar.gz
Report code coverage correctly for Kotlin methods with default arguments (#774)
Diffstat (limited to 'org.jacoco.core')
-rw-r--r--org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/AbstractMatcher.java6
-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/KotlinDefaultArgumentsFilter.java97
-rw-r--r--org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinGeneratedFilter.java6
-rw-r--r--org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/SyntheticFilter.java19
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());
}
}