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

Update filter for Kotlin 1.5 when-expressions with String #1172

Merged
merged 6 commits into from Apr 8, 2021
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 @@ -50,23 +50,27 @@ object KotlinWhenExpressionTarget {
else -> throw NoWhenBranchMatchedException() // assertNotCovered()
} // assertFullyCovered()

private fun whenString(p: String): Int = when (p) { // assertFullyCovered(0, 5)
private fun whenString(p: String): Int = when (p) { // assertFullyCovered(0, 7)
"a" -> 1 // assertFullyCovered()
"b" -> 2 // assertFullyCovered()
"\u0000a" -> 3 // assertFullyCovered()
"\u0000b" -> 4 // assertFullyCovered()
else -> 5 // assertFullyCovered()
"c" -> 3 // assertFullyCovered()
"\u0000a" -> 4 // assertFullyCovered()
"\u0000b" -> 5 // assertFullyCovered()
"\u0000c" -> 6 // assertFullyCovered()
else -> 7 // assertFullyCovered()
} // assertFullyCovered()

/**
* Unlike [whenString]
* in this example first case is the only case with biggest hashCode value.
*/
private fun whenStringBiggestHashCodeFirst(p: String): Int = when (p) { // assertFullyCovered(0, 4)
"b" -> 1 // assertFullyCovered()
"a" -> 2 // assertFullyCovered()
"\u0000a" -> 3 // assertFullyCovered()
else -> 4 // assertFullyCovered()
private fun whenStringBiggestHashCodeFirst(p: String): Int = when (p) { // assertFullyCovered(0, 6)
"c" -> 1 // assertFullyCovered()
"b" -> 2 // assertFullyCovered()
"\u0000b" -> 3 // assertFullyCovered()
"a" -> 4 // assertFullyCovered()
"\u0000a" -> 5 // assertFullyCovered()
else -> 6 // assertFullyCovered()
} // assertFullyCovered()

@JvmStatic
Expand All @@ -86,13 +90,17 @@ object KotlinWhenExpressionTarget {
whenString("")
whenString("a")
whenString("b")
whenString("c")
whenString("\u0000a")
whenString("\u0000b")
whenString("\u0000c")

whenStringBiggestHashCodeFirst("")
whenStringBiggestHashCodeFirst("a")
whenStringBiggestHashCodeFirst("b")
whenStringBiggestHashCodeFirst("c")
whenStringBiggestHashCodeFirst("\u0000a")
whenStringBiggestHashCodeFirst("\u0000b")
}

}
Expand Up @@ -23,11 +23,11 @@
import org.objectweb.asm.tree.MethodNode;

/**
* Unit tests for {@link StringSwitchEcjFilter}.
* Unit tests for {@link StringSwitchFilter}.
*/
public class StringSwitchEcjFilterTest extends FilterTestBase {
public class StringSwitchFilterTest extends FilterTestBase {

private final IFilter filter = new StringSwitchEcjFilter();
private final IFilter filter = new StringSwitchFilter();

@Test
public void should_filter() {
Expand Down Expand Up @@ -156,6 +156,119 @@ public void should_filter_when_default_is_first() {
assertIgnored(new Range(switchNode.getNext(), expectedToInclusive));
}

/**
* <pre>
* fun example(p: String) {
* when (p) {
* "a" -> return
* "\u0000a" -> return
* "b" -> return
* "\u0000b" -> return
* "c" -> return
* "\u0000c" -> return
* }
* }
* </pre>
*/
@Test
public void should_filter_Kotlin_1_5() {
final Set<AbstractInsnNode> expectedNewTargets = new HashSet<AbstractInsnNode>();

final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0,
"example", "()V", null, null);

final Label h1 = new Label();
final Label h2 = new Label();
final Label h3 = new Label();
final Label defaultCase = new Label();
final Label case1 = new Label();
final Label case2 = new Label();
final Label case3 = new Label();
final Label case4 = new Label();
final Label case5 = new Label();
final Label case6 = new Label();

m.visitVarInsn(Opcodes.ALOAD, 1);
m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "hashCode",
"()I", false);
m.visitTableSwitchInsn(97, 99, defaultCase, h1, h2, h3);

m.visitLabel(h1);
final AbstractInsnNode expectedFromInclusive = m.instructions.getLast();
m.visitVarInsn(Opcodes.ALOAD, 1);
m.visitLdcInsn("a");
m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "equals",
"(Ljava/lang/Object;)Z", false);
m.visitJumpInsn(Opcodes.IFNE, case1);

m.visitVarInsn(Opcodes.ALOAD, 1);
m.visitLdcInsn("\u0000a");
m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "equals",
"(Ljava/lang/Object;)Z", false);
m.visitJumpInsn(Opcodes.IFNE, case2);

m.visitJumpInsn(Opcodes.GOTO, defaultCase);

m.visitLabel(h2);
m.visitVarInsn(Opcodes.ALOAD, 1);
m.visitLdcInsn("b");
m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "equals",
"(Ljava/lang/Object;)Z", false);
m.visitJumpInsn(Opcodes.IFNE, case3);

m.visitVarInsn(Opcodes.ALOAD, 1);
m.visitLdcInsn("\u0000b");
m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "equals",
"(Ljava/lang/Object;)Z", false);
m.visitJumpInsn(Opcodes.IFNE, case4);

m.visitJumpInsn(Opcodes.GOTO, defaultCase);

m.visitLabel(h3);
m.visitVarInsn(Opcodes.ALOAD, 1);
m.visitLdcInsn("c");
m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "equals",
"(Ljava/lang/Object;)Z", false);
m.visitJumpInsn(Opcodes.IFNE, case5);

m.visitVarInsn(Opcodes.ALOAD, 1);
m.visitLdcInsn("\u0000c");
m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "equals",
"(Ljava/lang/Object;)Z", false);
m.visitJumpInsn(Opcodes.IFNE, case6);

m.visitJumpInsn(Opcodes.GOTO, defaultCase);
final AbstractInsnNode expectedToInclusive = m.instructions.getLast();

m.visitLabel(case1);
m.visitInsn(Opcodes.RETURN);
expectedNewTargets.add(m.instructions.getLast());
m.visitLabel(case2);
m.visitInsn(Opcodes.RETURN);
expectedNewTargets.add(m.instructions.getLast());
m.visitLabel(case3);
m.visitInsn(Opcodes.RETURN);
expectedNewTargets.add(m.instructions.getLast());
m.visitLabel(case4);
m.visitInsn(Opcodes.RETURN);
expectedNewTargets.add(m.instructions.getLast());
m.visitLabel(case5);
m.visitInsn(Opcodes.RETURN);
expectedNewTargets.add(m.instructions.getLast());
m.visitLabel(case6);
m.visitInsn(Opcodes.RETURN);
expectedNewTargets.add(m.instructions.getLast());
m.visitLabel(defaultCase);
m.visitInsn(Opcodes.RETURN);
expectedNewTargets.add(m.instructions.getLast());

filter.filter(m, context, output);

assertIgnored(new Range(expectedFromInclusive, expectedToInclusive));
assertReplacedBranches(expectedFromInclusive.getPrevious(),
expectedNewTargets);
}

@Test
public void should_not_filter_empty_lookup_switch() {
final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0,
Expand Down
Expand Up @@ -38,7 +38,7 @@ public static IFilter all() {
new TryWithResourcesJavacFilter(),
new TryWithResourcesEcjFilter(), new FinallyFilter(),
new PrivateEmptyNoArgConstructorFilter(),
new StringSwitchJavacFilter(), new StringSwitchEcjFilter(),
new StringSwitchJavacFilter(), new StringSwitchFilter(),
new EnumEmptyConstructorFilter(), new RecordsFilter(),
new AnnotationGeneratedFilter(), new KotlinGeneratedFilter(),
new KotlinLateinitFilter(), new KotlinWhenFilter(),
Expand Down
Expand Up @@ -26,9 +26,10 @@

/**
* Filters code that is generated by ECJ for a <code>switch</code> statement
* with a <code>String</code>.
* with a <code>String</code> and by Kotlin compiler 1.5 and above for a
* <code>when</code> expression with a <code>String</code>.
*/
public final class StringSwitchEcjFilter implements IFilter {
public final class StringSwitchFilter implements IFilter {

public void filter(final MethodNode methodNode,
final IFilterContext context, final IFilterOutput output) {
Expand All @@ -42,7 +43,8 @@ private static class Matcher extends AbstractMatcher {
public void match(final AbstractInsnNode start,
final IFilterOutput output) {

if (Opcodes.ASTORE != start.getOpcode()) {
if (start.getOpcode() != /* ECJ */ Opcodes.ASTORE
&& start.getOpcode() != /* Kotlin */ Opcodes.ALOAD) {
return;
}
cursor = start;
Expand Down
8 changes: 6 additions & 2 deletions org.jacoco.doc/docroot/doc/changes.html
Expand Up @@ -39,8 +39,12 @@ <h3>New Features</h3>
<li>Branch added by the Kotlin compiler version 1.5.0 and above for reading from
<code>lateinit</code> property is filtered out during generation of report
(GitHub <a href="https://github.com/jacoco/jacoco/issues/1166">#1166</a>).</li>
<li>Improved filtering of bytecode generated by Kotlin compiler for
<code>when</code> expressions on <code>kotlin.String</code> values
<li>Additional bytecode generated by the Kotlin compiler version 1.5.0 and above
for <code>when</code> expressions on <code>kotlin.String</code> values
is filtered out during generation of report
(GitHub <a href="https://github.com/jacoco/jacoco/issues/1172">#1172</a>).</li>
<li>Improved filtering of bytecode generated by Kotlin compiler versions below
1.5.0 for <code>when</code> expressions on <code>kotlin.String</code> values
(GitHub <a href="https://github.com/jacoco/jacoco/issues/1156">#1156</a>).</li>
</ul>

Expand Down