Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Do not filter out synthetic constructors that contain values of default arguments in Kotlin #888

Merged
merged 8 commits into from Jun 5, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Side note: When I see this I more and more like @Godin 's idea to separate filters for different compilers.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@marchof don't know about whom you're talking 😆

Not sure that separation can remove this dependency between filters, i.e. not every but some synthetic methods anyway should be filtered in Kotlin.

.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