Skip to content

Commit

Permalink
Update filter for Kotlin 1.5 when-expressions with String (#1172)
Browse files Browse the repository at this point in the history
  • Loading branch information
Godin committed Apr 8, 2021
1 parent 9a88237 commit b68fe1a
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 18 deletions.
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

0 comments on commit b68fe1a

Please sign in to comment.