Skip to content

Commit

Permalink
Do not filter out synthetic constructors that contain values of defau…
Browse files Browse the repository at this point in the history
…lt arguments in Kotlin (#888)
  • Loading branch information
Godin authored and marchof committed Jun 5, 2019
1 parent fcb8ba1 commit 219088d
Show file tree
Hide file tree
Showing 6 changed files with 95 additions and 12 deletions.
Expand Up @@ -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<String>) {
f(a = "a")
Expand All @@ -38,6 +42,9 @@ object KotlinDefaultArgumentsTarget {
branch(true)

Open().f()

Constructor(false)
Constructor(true)
}

}
Expand Up @@ -150,4 +150,38 @@ public void should_filter_open_functions() {
new Range(m.instructions.get(11), m.instructions.get(11)));
}

/**
* <pre>
* class C(a: Int = 42)
* </pre>
*/
@Test
public void should_filter_constructors() {
final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION,
Opcodes.ACC_SYNTHETIC, "<init>",
"(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", "<init>", "(I)V",
false);
m.visitInsn(Opcodes.RETURN);

filter.filter(m, context, output);

assertIgnored(new Range(m.instructions.get(3), m.instructions.get(3)));
}

}
Expand Up @@ -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, "<init>",
"(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,
Expand Down
Expand Up @@ -38,34 +38,49 @@
* </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.
* 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
* <code>IFEQ</code> 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 (!"<init>".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,
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);
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);
Expand All @@ -91,7 +106,7 @@ public void match(final MethodNode methodNode,

final Set<AbstractInsnNode> ignore = new HashSet<AbstractInsnNode>();
final int maskVar = Type.getMethodType(methodNode.desc)
.getArgumentTypes().length - 2;
.getArgumentTypes().length - (constructor ? 1 : 2);
while (true) {
if (cursor.getOpcode() != Opcodes.ILOAD) {
break;
Expand Down
Expand Up @@ -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;
}

Expand Down
7 changes: 7 additions & 0 deletions org.jacoco.doc/docroot/doc/changes.html
Expand Up @@ -27,6 +27,13 @@ <h3>New Features</h3>
(GitHub <a href="https://github.com/jacoco/jacoco/issues/887">#887</a>).</li>
</ul>

<h3>Fixed bugs</h3>
<ul>
<li><code>synthetic</code> constructors that contain values of default arguments
in Kotlin should not be ignored
(GitHub <a href="https://github.com/jacoco/jacoco/issues/888">#888</a>).</li>
</ul>

<h2>Release 0.8.4 (2019/05/08)</h2>

<h3>New Features</h3>
Expand Down

0 comments on commit 219088d

Please sign in to comment.