diff --git a/org.jacoco.core.test.validation.kotlin/src/org/jacoco/core/test/validation/kotlin/targets/KotlinWhenExpressionTarget.kt b/org.jacoco.core.test.validation.kotlin/src/org/jacoco/core/test/validation/kotlin/targets/KotlinWhenExpressionTarget.kt index 882bac63af..13f7e20d1e 100644 --- a/org.jacoco.core.test.validation.kotlin/src/org/jacoco/core/test/validation/kotlin/targets/KotlinWhenExpressionTarget.kt +++ b/org.jacoco.core.test.validation.kotlin/src/org/jacoco/core/test/validation/kotlin/targets/KotlinWhenExpressionTarget.kt @@ -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 @@ -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") } } diff --git a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/StringSwitchEcjFilterTest.java b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/StringSwitchFilterTest.java similarity index 59% rename from org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/StringSwitchEcjFilterTest.java rename to org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/StringSwitchFilterTest.java index 79c353a093..44bb10f150 100644 --- a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/StringSwitchEcjFilterTest.java +++ b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/StringSwitchFilterTest.java @@ -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() { @@ -156,6 +156,119 @@ public void should_filter_when_default_is_first() { assertIgnored(new Range(switchNode.getNext(), expectedToInclusive)); } + /** + *
+	 * fun example(p: String) {
+	 *   when (p) {
+	 *     "a" -> return
+	 *     "\u0000a" -> return
+	 *     "b" -> return
+	 *     "\u0000b" -> return
+	 *     "c" -> return
+	 *     "\u0000c" -> return
+	 *   }
+	 * }
+	 * 
+ */ + @Test + public void should_filter_Kotlin_1_5() { + final Set expectedNewTargets = new HashSet(); + + 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, diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/Filters.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/Filters.java index 4a7c18fa56..484ccc0032 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/Filters.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/Filters.java @@ -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(), diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/StringSwitchEcjFilter.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/StringSwitchFilter.java similarity index 91% rename from org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/StringSwitchEcjFilter.java rename to org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/StringSwitchFilter.java index 47750f0c27..a78e253ee8 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/StringSwitchEcjFilter.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/StringSwitchFilter.java @@ -26,9 +26,10 @@ /** * Filters code that is generated by ECJ for a switch statement - * with a String. + * with a String and by Kotlin compiler 1.5 and above for a + * when expression with a String. */ -public final class StringSwitchEcjFilter implements IFilter { +public final class StringSwitchFilter implements IFilter { public void filter(final MethodNode methodNode, final IFilterContext context, final IFilterOutput output) { @@ -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; diff --git a/org.jacoco.doc/docroot/doc/changes.html b/org.jacoco.doc/docroot/doc/changes.html index 15dfe2c732..2f8ab6fcbc 100644 --- a/org.jacoco.doc/docroot/doc/changes.html +++ b/org.jacoco.doc/docroot/doc/changes.html @@ -39,8 +39,12 @@

New Features

  • Branch added by the Kotlin compiler version 1.5.0 and above for reading from lateinit property is filtered out during generation of report (GitHub #1166).
  • -
  • Improved filtering of bytecode generated by Kotlin compiler for - when expressions on kotlin.String values +
  • Additional bytecode generated by the Kotlin compiler version 1.5.0 and above + for when expressions on kotlin.String values + is filtered out during generation of report + (GitHub #1172).
  • +
  • Improved filtering of bytecode generated by Kotlin compiler versions below + 1.5.0 for when expressions on kotlin.String values (GitHub #1156).