diff --git a/org.jacoco.core.test.validation.kotlin/src/org/jacoco/core/test/validation/kotlin/targets/KotlinDefaultArgumentsTarget.kt b/org.jacoco.core.test.validation.kotlin/src/org/jacoco/core/test/validation/kotlin/targets/KotlinDefaultArgumentsTarget.kt index c29228122a..1a89eaba36 100644 --- a/org.jacoco.core.test.validation.kotlin/src/org/jacoco/core/test/validation/kotlin/targets/KotlinDefaultArgumentsTarget.kt +++ b/org.jacoco.core.test.validation.kotlin/src/org/jacoco/core/test/validation/kotlin/targets/KotlinDefaultArgumentsTarget.kt @@ -27,6 +27,10 @@ object KotlinDefaultArgumentsTarget { } } + class Constructor() { + constructor(a: Boolean, b: String = if (a) "a" else "b") : this() // assertFullyCovered(0, 2) + } + @JvmStatic fun main(args: Array) { f(a = "a") @@ -38,6 +42,9 @@ object KotlinDefaultArgumentsTarget { branch(true) Open().f() + + Constructor(false) + Constructor(true) } } diff --git a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/KotlinDefaultArgumentsFilterTest.java b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/KotlinDefaultArgumentsFilterTest.java index 8e79a5a78b..721abfb029 100644 --- a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/KotlinDefaultArgumentsFilterTest.java +++ b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/KotlinDefaultArgumentsFilterTest.java @@ -150,4 +150,38 @@ public void should_filter_open_functions() { new Range(m.instructions.get(11), m.instructions.get(11))); } + /** + *
+	 * class C(a: Int = 42)
+	 * 
+ */ + @Test + public void should_filter_constructors() { + final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, + Opcodes.ACC_SYNTHETIC, "", + "(IILkotlin/jvm/internal/DefaultConstructorMarker;)V", null, + null); + context.classAnnotations + .add(KotlinGeneratedFilter.KOTLIN_METADATA_DESC); + + m.visitVarInsn(Opcodes.ILOAD, 2); + m.visitInsn(Opcodes.ICONST_1); + m.visitInsn(Opcodes.IAND); + Label label = new Label(); + m.visitJumpInsn(Opcodes.IFEQ, label); + // default argument + m.visitLdcInsn(Integer.valueOf(42)); + m.visitVarInsn(Opcodes.ISTORE, 1); + m.visitLabel(label); + m.visitVarInsn(Opcodes.ALOAD, 0); + m.visitVarInsn(Opcodes.ILOAD, 1); + m.visitMethodInsn(Opcodes.INVOKESPECIAL, "Owner", "", "(I)V", + false); + m.visitInsn(Opcodes.RETURN); + + filter.filter(m, context, output); + + assertIgnored(new Range(m.instructions.get(3), m.instructions.get(3))); + } + } diff --git a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/SyntheticFilterTest.java b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/SyntheticFilterTest.java index 5bbae9839d..5b0a30ba3a 100644 --- a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/SyntheticFilterTest.java +++ b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/SyntheticFilterTest.java @@ -82,6 +82,21 @@ public void should_filter_synthetic_method_with_suffix_default_in_non_kotlin_cla assertMethodIgnored(m); } + @Test + public void should_not_filter_synthetic_constructor_containing_default_arguments_in_kotlin_classes() { + final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, + Opcodes.ACC_SYNTHETIC, "", + "(IILkotlin/jvm/internal/DefaultConstructorMarker;)V", null, + null); + context.classAnnotations + .add(KotlinGeneratedFilter.KOTLIN_METADATA_DESC); + m.visitInsn(Opcodes.NOP); + + filter.filter(m, context, output); + + assertIgnored(); + } + @Test public void should_not_filter_synthetic_methods_whose_last_argument_is_kotlin_coroutine_continuation() { final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 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 index 34f9562c6c..a7a05cf75b 100644 --- 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 @@ -38,14 +38,28 @@ * * * Where maskVar 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 IFEQ - * instructions should be ignored. + * suffix "$default" or of synthetic constructor with last argument + * "kotlin.jvm.internal.DefaultConstructorMarker". And its value can't be zero - + * invocation with all arguments uses original non synthetic method, thus + * IFEQ instructions should be ignored. */ public final class KotlinDefaultArgumentsFilter implements IFilter { - static boolean isDefaultArgumentsMethodName(final String methodName) { - return methodName.endsWith("$default"); + static boolean isDefaultArgumentsMethod(final MethodNode methodNode) { + return methodNode.name.endsWith("$default"); + } + + static boolean isDefaultArgumentsConstructor(final MethodNode methodNode) { + if (!"".equals(methodNode.name)) { + return false; + } + final Type[] argumentTypes = Type.getMethodType(methodNode.desc) + .getArgumentTypes(); + if (argumentTypes.length < 2) { + return false; + } + return "kotlin.jvm.internal.DefaultConstructorMarker" + .equals(argumentTypes[argumentTypes.length - 1].getClassName()); } public void filter(final MethodNode methodNode, @@ -53,19 +67,20 @@ public void filter(final MethodNode methodNode, if ((methodNode.access & Opcodes.ACC_SYNTHETIC) == 0) { return; } - if (!isDefaultArgumentsMethodName(methodNode.name)) { - return; - } if (!KotlinGeneratedFilter.isKotlinClass(context)) { return; } - new Matcher().match(methodNode, output); + if (isDefaultArgumentsMethod(methodNode)) { + new Matcher().match(methodNode, output, false); + } else if (isDefaultArgumentsConstructor(methodNode)) { + new Matcher().match(methodNode, output, true); + } } private static class Matcher extends AbstractMatcher { public void match(final MethodNode methodNode, - final IFilterOutput output) { + final IFilterOutput output, final boolean constructor) { cursor = methodNode.instructions.getFirst(); nextIs(Opcodes.IFNULL); @@ -91,7 +106,7 @@ public void match(final MethodNode methodNode, final Set ignore = new HashSet(); final int maskVar = Type.getMethodType(methodNode.desc) - .getArgumentTypes().length - 2; + .getArgumentTypes().length - (constructor ? 1 : 2); while (true) { if (cursor.getOpcode() != Opcodes.ILOAD) { break; 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 69c4092a8d..46d4e6eb77 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 @@ -31,7 +31,12 @@ public void filter(final MethodNode methodNode, if (KotlinGeneratedFilter.isKotlinClass(context)) { if (KotlinDefaultArgumentsFilter - .isDefaultArgumentsMethodName(methodNode.name)) { + .isDefaultArgumentsMethod(methodNode)) { + return; + } + + if (KotlinDefaultArgumentsFilter + .isDefaultArgumentsConstructor(methodNode)) { return; } diff --git a/org.jacoco.doc/docroot/doc/changes.html b/org.jacoco.doc/docroot/doc/changes.html index 252d7db7c7..25087b3a07 100644 --- a/org.jacoco.doc/docroot/doc/changes.html +++ b/org.jacoco.doc/docroot/doc/changes.html @@ -27,6 +27,13 @@

New Features

(GitHub #887). +

Fixed bugs

+
    +
  • synthetic constructors that contain values of default arguments + in Kotlin should not be ignored + (GitHub #888).
  • +
+

Release 0.8.4 (2019/05/08)

New Features