From ce476a101de587d3061f5aefe808f3ebe0118855 Mon Sep 17 00:00:00 2001 From: ChristopherKoellner Date: Thu, 28 Oct 2021 11:05:45 +0200 Subject: [PATCH 1/5] Extend koltin lateinit filtering --- .../kotlin/targets/KotlinLateinitTarget.kt | 5 ++- .../filter/KotlinLateinitFilterTest.java | 37 +++++++++++++++++++ .../analysis/filter/KotlinLateinitFilter.java | 6 ++- 3 files changed, 45 insertions(+), 3 deletions(-) diff --git a/org.jacoco.core.test.validation.kotlin/src/org/jacoco/core/test/validation/kotlin/targets/KotlinLateinitTarget.kt b/org.jacoco.core.test.validation.kotlin/src/org/jacoco/core/test/validation/kotlin/targets/KotlinLateinitTarget.kt index 050fbb5075..7c8c7522a2 100644 --- a/org.jacoco.core.test.validation.kotlin/src/org/jacoco/core/test/validation/kotlin/targets/KotlinLateinitTarget.kt +++ b/org.jacoco.core.test.validation.kotlin/src/org/jacoco/core/test/validation/kotlin/targets/KotlinLateinitTarget.kt @@ -18,11 +18,12 @@ import org.jacoco.core.test.validation.targets.Stubs.nop * This test target is `lateinit` property. */ object KotlinLateinitTarget { - private lateinit var x: String + private class X + private lateinit var x: X @JvmStatic fun main(args: Array) { - x = "" + x = X() nop(x) // assertFullyCovered() } } diff --git a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/KotlinLateinitFilterTest.java b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/KotlinLateinitFilterTest.java index 85cbd16c86..a2e5a75869 100644 --- a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/KotlinLateinitFilterTest.java +++ b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/KotlinLateinitFilterTest.java @@ -96,4 +96,41 @@ public void should_filter_Kotlin_1_5() { assertIgnored(new Range(expectedFrom, expectedTo)); } + /** + *
+	 * class Example {
+	 *   class X
+	 *   private lateinit var x: X
+	 *   fun example() = x
+	 * }
+	 * 
+ */ + @Test + public void should_filter_Kotlin_1_5_custom_class() { + final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0, + "example", "()L/Example$X;", null, null); + Label label = new Label(); + m.visitVarInsn(Opcodes.ALOAD, 0); + m.visitFieldInsn(Opcodes.GETFIELD, "Example", "x", "L/Example$X;"); + m.visitVarInsn(Opcodes.ASTORE, 1); + m.visitVarInsn(Opcodes.ALOAD, 1); + m.visitJumpInsn(Opcodes.IFNONNULL, label); + final AbstractInsnNode expectedFrom = m.instructions.getLast(); + m.visitLdcInsn("x"); + m.visitMethodInsn(Opcodes.INVOKESTATIC, + "kotlin/jvm/internal/Intrinsics", + "throwUninitializedPropertyAccessException", + "(Ljava/lang/String;)V", false); + m.visitInsn(Opcodes.ACONST_NULL); + m.visitInsn(Opcodes.GOTO); + final AbstractInsnNode expectedTo = m.instructions.getLast(); + m.visitLabel(label); + m.visitVarInsn(Opcodes.ALOAD, 1); + m.visitInsn(Opcodes.ARETURN); + + filter.filter(m, context, output); + + assertIgnored(new Range(expectedFrom, expectedTo)); + } + } diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinLateinitFilter.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinLateinitFilter.java index a5e2026546..b6b239e666 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinLateinitFilter.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinLateinitFilter.java @@ -49,7 +49,11 @@ public void match(final AbstractInsnNode start, && skipNonOpcodes(cursor.getNext()) != skipNonOpcodes( ((JumpInsnNode) start).label)) { nextIs(Opcodes.ACONST_NULL); - nextIs(Opcodes.ATHROW); + if (cursor.getNext().getOpcode() == Opcodes.ATHROW) { + nextIs(Opcodes.ATHROW); + } else { + nextIs(Opcodes.GOTO); + } } if (cursor != null) { From bff99246ea7365b6fbd12a13d7d749393b88f908 Mon Sep 17 00:00:00 2001 From: ChristopherKoellner Date: Thu, 28 Oct 2021 17:57:41 +0200 Subject: [PATCH 2/5] Extend koltin lateinit filtering for generics types --- .../filter/KotlinLateinitFilterTest.java | 37 +++++++++++++++++++ .../analysis/filter/KotlinLateinitFilter.java | 12 +++++- 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/KotlinLateinitFilterTest.java b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/KotlinLateinitFilterTest.java index a2e5a75869..23f40171b8 100644 --- a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/KotlinLateinitFilterTest.java +++ b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/KotlinLateinitFilterTest.java @@ -133,4 +133,41 @@ public void should_filter_Kotlin_1_5_custom_class() { assertIgnored(new Range(expectedFrom, expectedTo)); } + /** + *
+	 * class Example {
+	 *   private lateinit var x: T
+	 *   fun example() = x
+	 * }
+	 * 
+ */ + @Test + public void should_filter_Kotlin_1_5_generics() { + final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0, + "example", "()Ljava/lang/Object;", null, null); + Label label = new Label(); + m.visitVarInsn(Opcodes.ALOAD, 0); + m.visitFieldInsn(Opcodes.GETFIELD, "Example", "x", + "Ljava/lang/Object;"); + m.visitVarInsn(Opcodes.ASTORE, 1); + m.visitVarInsn(Opcodes.ALOAD, 1); + m.visitJumpInsn(Opcodes.IFNONNULL, label); + final AbstractInsnNode expectedFrom = m.instructions.getLast(); + m.visitLdcInsn("x"); + m.visitMethodInsn(Opcodes.INVOKESTATIC, + "kotlin/jvm/internal/Intrinsics", + "throwUninitializedPropertyAccessException", + "(Ljava/lang/String;)V", false); + m.visitInsn(Opcodes.GETSTATIC); + m.visitInsn(Opcodes.GOTO); + final AbstractInsnNode expectedTo = m.instructions.getLast(); + m.visitLabel(label); + m.visitVarInsn(Opcodes.ALOAD, 1); + m.visitInsn(Opcodes.ARETURN); + + filter.filter(m, context, output); + + assertIgnored(new Range(expectedFrom, expectedTo)); + } + } diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinLateinitFilter.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinLateinitFilter.java index b6b239e666..8f43de173d 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinLateinitFilter.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinLateinitFilter.java @@ -48,8 +48,16 @@ public void match(final AbstractInsnNode start, if (cursor != null && skipNonOpcodes(cursor.getNext()) != skipNonOpcodes( ((JumpInsnNode) start).label)) { - nextIs(Opcodes.ACONST_NULL); - if (cursor.getNext().getOpcode() == Opcodes.ATHROW) { + + if (cursor != null && cursor.getNext() != null + && cursor.getNext().getOpcode() == Opcodes.GETSTATIC) { + nextIs(Opcodes.GETSTATIC); + } else { + nextIs(Opcodes.ACONST_NULL); + } + + if (cursor != null && cursor.getNext() != null + && cursor.getNext().getOpcode() == Opcodes.ATHROW) { nextIs(Opcodes.ATHROW); } else { nextIs(Opcodes.GOTO); From 9e4cf1c830d8dd140231c3d23fa1a53027d98e19 Mon Sep 17 00:00:00 2001 From: ChristopherKoellner Date: Fri, 29 Oct 2021 22:07:06 +0200 Subject: [PATCH 3/5] Refactored KotlinLateinitFilter.java and integrated feedback --- .../kotlin/targets/KotlinLateinitTarget.kt | 18 +++- .../filter/KotlinLateinitFilterTest.java | 91 +++++++++++++++++-- .../analysis/filter/KotlinLateinitFilter.java | 69 ++++++++++---- 3 files changed, 148 insertions(+), 30 deletions(-) diff --git a/org.jacoco.core.test.validation.kotlin/src/org/jacoco/core/test/validation/kotlin/targets/KotlinLateinitTarget.kt b/org.jacoco.core.test.validation.kotlin/src/org/jacoco/core/test/validation/kotlin/targets/KotlinLateinitTarget.kt index 7c8c7522a2..449678dabc 100644 --- a/org.jacoco.core.test.validation.kotlin/src/org/jacoco/core/test/validation/kotlin/targets/KotlinLateinitTarget.kt +++ b/org.jacoco.core.test.validation.kotlin/src/org/jacoco/core/test/validation/kotlin/targets/KotlinLateinitTarget.kt @@ -18,12 +18,24 @@ import org.jacoco.core.test.validation.targets.Stubs.nop * This test target is `lateinit` property. */ object KotlinLateinitTarget { - private class X - private lateinit var x: X + private lateinit var x: String + lateinit var y: String + + private class Example { + private lateinit var x: T + fun nop() { + x = Any() as T + nop(x) + } + } @JvmStatic fun main(args: Array) { - x = X() + x = "" + y = "" + nop(x) // assertFullyCovered() + nop(y) // assertFullyCovered() + Example().nop() // assertFullyCovered() } } diff --git a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/KotlinLateinitFilterTest.java b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/KotlinLateinitFilterTest.java index 23f40171b8..5e4b7e6cef 100644 --- a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/KotlinLateinitFilterTest.java +++ b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/KotlinLateinitFilterTest.java @@ -99,19 +99,55 @@ public void should_filter_Kotlin_1_5() { /** *
 	 * class Example {
-	 *   class X
-	 *   private lateinit var x: X
+	 *   lateinit var x: String
+	 * }
+	 * 
+ */ + @Test + public void should_filter_Kotlin_1_5_public() { + final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0, + "example", "()Ljava/lang/String;", null, null); + Label label = new Label(); + m.visitVarInsn(Opcodes.ALOAD, 0); + m.visitFieldInsn(Opcodes.GETFIELD, "Example", "x", + "Ljava/lang/String;"); + m.visitVarInsn(Opcodes.ASTORE, 1); + m.visitVarInsn(Opcodes.ALOAD, 1); + m.visitJumpInsn(Opcodes.IFNULL, label); + final AbstractInsnNode expectedFrom = m.instructions.getLast(); + m.visitVarInsn(Opcodes.ALOAD, 1); + m.visitInsn(Opcodes.ARETURN); + m.visitLdcInsn("x"); + m.visitMethodInsn(Opcodes.INVOKESTATIC, + "kotlin/jvm/internal/Intrinsics", + "throwUninitializedPropertyAccessException", + "(Ljava/lang/String;)V", false); + m.visitInsn(Opcodes.ACONST_NULL); + m.visitInsn(Opcodes.ATHROW); + final AbstractInsnNode expectedTo = m.instructions.getLast(); + m.visitLabel(label); + + filter.filter(m, context, output); + + assertIgnored(new Range(expectedFrom, expectedTo)); + } + + /** + *
+	 * class Example {
+	 *   private lateinit var x: String
 	 *   fun example() = x
 	 * }
 	 * 
*/ @Test - public void should_filter_Kotlin_1_5_custom_class() { + public void should_filter_Kotlin_1_5_30() { final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0, - "example", "()L/Example$X;", null, null); + "example", "()Ljava/lang/String;", null, null); Label label = new Label(); m.visitVarInsn(Opcodes.ALOAD, 0); - m.visitFieldInsn(Opcodes.GETFIELD, "Example", "x", "L/Example$X;"); + m.visitFieldInsn(Opcodes.GETFIELD, "Example", "x", + "Ljava/lang/String;"); m.visitVarInsn(Opcodes.ASTORE, 1); m.visitVarInsn(Opcodes.ALOAD, 1); m.visitJumpInsn(Opcodes.IFNONNULL, label); @@ -122,7 +158,7 @@ public void should_filter_Kotlin_1_5_custom_class() { "throwUninitializedPropertyAccessException", "(Ljava/lang/String;)V", false); m.visitInsn(Opcodes.ACONST_NULL); - m.visitInsn(Opcodes.GOTO); + m.visitJumpInsn(Opcodes.GOTO, label); final AbstractInsnNode expectedTo = m.instructions.getLast(); m.visitLabel(label); m.visitVarInsn(Opcodes.ALOAD, 1); @@ -133,6 +169,42 @@ public void should_filter_Kotlin_1_5_custom_class() { assertIgnored(new Range(expectedFrom, expectedTo)); } + /** + *
+	 * class Example {
+	 *   lateinit var x: String
+	 * }
+	 * 
+ */ + @Test + public void should_filter_Kotlin_1_5_30_public() { + final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0, + "example", "()Ljava/lang/String;", null, null); + Label label = new Label(); + m.visitVarInsn(Opcodes.ALOAD, 0); + m.visitFieldInsn(Opcodes.GETFIELD, "Example", "x", + "Ljava/lang/String;"); + m.visitVarInsn(Opcodes.ASTORE, 1); + m.visitVarInsn(Opcodes.ALOAD, 1); + m.visitJumpInsn(Opcodes.IFNULL, label); + final AbstractInsnNode expectedFrom = m.instructions.getLast(); + m.visitVarInsn(Opcodes.ALOAD, 1); + m.visitInsn(Opcodes.ARETURN); + m.visitLdcInsn("x"); + m.visitMethodInsn(Opcodes.INVOKESTATIC, + "kotlin/jvm/internal/Intrinsics", + "throwUninitializedPropertyAccessException", + "(Ljava/lang/String;)V", false); + m.visitInsn(Opcodes.ACONST_NULL); + m.visitInsn(Opcodes.ARETURN); + final AbstractInsnNode expectedTo = m.instructions.getLast(); + m.visitLabel(label); + + filter.filter(m, context, output); + + assertIgnored(new Range(expectedFrom, expectedTo)); + } + /** *
 	 * class Example {
@@ -142,7 +214,7 @@ public void should_filter_Kotlin_1_5_custom_class() {
 	 * 
*/ @Test - public void should_filter_Kotlin_1_5_generics() { + public void should_filter_Kotlin_1_5_30_generics() { final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0, "example", "()Ljava/lang/Object;", null, null); Label label = new Label(); @@ -158,8 +230,9 @@ public void should_filter_Kotlin_1_5_generics() { "kotlin/jvm/internal/Intrinsics", "throwUninitializedPropertyAccessException", "(Ljava/lang/String;)V", false); - m.visitInsn(Opcodes.GETSTATIC); - m.visitInsn(Opcodes.GOTO); + m.visitFieldInsn(Opcodes.GETSTATIC, "kotlin/Unit", "INSTANCE", + "Lkotlin/Unit;"); + m.visitJumpInsn(Opcodes.GOTO, label); final AbstractInsnNode expectedTo = m.instructions.getLast(); m.visitLabel(label); m.visitVarInsn(Opcodes.ALOAD, 1); diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinLateinitFilter.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinLateinitFilter.java index 8f43de173d..56be21edf3 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinLateinitFilter.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinLateinitFilter.java @@ -27,19 +27,29 @@ public void filter(final MethodNode methodNode, final IFilterContext context, final IFilterOutput output) { final Matcher matcher = new Matcher(); for (final AbstractInsnNode node : methodNode.instructions) { - matcher.match(node, output); + final AbstractInsnNode to = matcher.match(node); + if (to != null) { + output.ignore(node, to); + } } } private static class Matcher extends AbstractMatcher { - public void match(final AbstractInsnNode start, - final IFilterOutput output) { - if (Opcodes.IFNONNULL != start.getOpcode()) { - return; + public AbstractInsnNode match(final AbstractInsnNode start) { + + if (Opcodes.IFNONNULL != start.getOpcode() + && Opcodes.IFNULL != start.getOpcode()) { + return null; } + cursor = start; + if (Opcodes.IFNULL == start.getOpcode()) { + nextIs(Opcodes.ALOAD); + nextIs(Opcodes.ARETURN); + } + nextIs(Opcodes.LDC); nextIsInvoke(Opcodes.INVOKESTATIC, "kotlin/jvm/internal/Intrinsics", "throwUninitializedPropertyAccessException", @@ -49,24 +59,47 @@ public void match(final AbstractInsnNode start, && skipNonOpcodes(cursor.getNext()) != skipNonOpcodes( ((JumpInsnNode) start).label)) { - if (cursor != null && cursor.getNext() != null - && cursor.getNext().getOpcode() == Opcodes.GETSTATIC) { - nextIs(Opcodes.GETSTATIC); - } else { - nextIs(Opcodes.ACONST_NULL); + final AbstractInsnNode node = cursor; + if (isKotlin1_5(node) || isKotlin1_5_30(node)) { + return cursor; } + return null; + } + return cursor; + } - if (cursor != null && cursor.getNext() != null - && cursor.getNext().getOpcode() == Opcodes.ATHROW) { - nextIs(Opcodes.ATHROW); - } else { - nextIs(Opcodes.GOTO); + private boolean isKotlin1_5(AbstractInsnNode node) { + cursor = node; + nextIs(Opcodes.ACONST_NULL); + nextIs(Opcodes.ATHROW); + return cursor != null; + } + + private boolean isKotlin1_5_30(AbstractInsnNode node) { + cursor = node; + if (cursor.getNext() != null) { + switch (cursor.getNext().getOpcode()) { + case Opcodes.GETSTATIC: + nextIsField(Opcodes.GETSTATIC, "kotlin/Unit", "INSTANCE", + "Lkotlin/Unit;"); + break; + case Opcodes.ACONST_NULL: + nextIs(Opcodes.ACONST_NULL); + break; } } - - if (cursor != null) { - output.ignore(start, cursor); + if (cursor.getNext() != null) { + switch (cursor.getNext().getOpcode()) { + case Opcodes.GOTO: + nextIs(Opcodes.GOTO); + break; + case Opcodes.ARETURN: + nextIs(Opcodes.ARETURN); + break; + } } + return cursor != null; } + } } From d00eadd88eabde9c1faa35638bd40475593d1737 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20R=C3=B6ssler?= Date: Fri, 19 Nov 2021 18:15:16 +0100 Subject: [PATCH 4/5] establish Kotlin 1.6 compatibility --- .../kotlin/targets/KotlinLateinitTarget.kt | 11 +- .../filter/KotlinLateinitFilterTest.java | 200 ++++++++++++++---- .../analysis/filter/KotlinLateinitFilter.java | 71 +++---- 3 files changed, 197 insertions(+), 85 deletions(-) diff --git a/org.jacoco.core.test.validation.kotlin/src/org/jacoco/core/test/validation/kotlin/targets/KotlinLateinitTarget.kt b/org.jacoco.core.test.validation.kotlin/src/org/jacoco/core/test/validation/kotlin/targets/KotlinLateinitTarget.kt index 449678dabc..c57bfa41f0 100644 --- a/org.jacoco.core.test.validation.kotlin/src/org/jacoco/core/test/validation/kotlin/targets/KotlinLateinitTarget.kt +++ b/org.jacoco.core.test.validation.kotlin/src/org/jacoco/core/test/validation/kotlin/targets/KotlinLateinitTarget.kt @@ -23,9 +23,18 @@ object KotlinLateinitTarget { private class Example { private lateinit var x: T + private lateinit var xx: Any + lateinit var y: T + lateinit var yy: Any fun nop() { x = Any() as T - nop(x) + xx = Any() + y = Any() as T + yy = Any() + nop(x) // assertFullyCovered() + nop(xx) // assertFullyCovered() + nop(y) // assertFullyCovered() + nop(yy) // assertFullyCovered() } } diff --git a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/KotlinLateinitFilterTest.java b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/KotlinLateinitFilterTest.java index 5e4b7e6cef..c78f8d741c 100644 --- a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/KotlinLateinitFilterTest.java +++ b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/KotlinLateinitFilterTest.java @@ -49,8 +49,8 @@ public void testLateinitBranchIsFiltered() { "kotlin/jvm/internal/Intrinsics", "throwUninitializedPropertyAccessException", "(Ljava/lang/String;)V", false); - final AbstractInsnNode expectedTo = m.instructions.getLast(); m.visitLabel(l2); + final AbstractInsnNode expectedTo = m.instructions.getLast(); m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "android/os/PowerManager$WakeLock", "acquire", "", false); @@ -61,33 +61,34 @@ public void testLateinitBranchIsFiltered() { /** *
-	 * class Example {
-	 *   private lateinit var x: String
-	 *   fun example() = x
+	 * class LateinitStringPrivate {
+	 *     private lateinit var member: String
+	 *
+	 *     fun get(): String = member
 	 * }
 	 * 
*/ @Test - public void should_filter_Kotlin_1_5() { + public void should_filter_Kotlin_1_5_private() { final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0, - "example", "()Ljava/lang/String;", null, null); + "get", "()Ljava/lang/String;", null, null); Label label = new Label(); m.visitVarInsn(Opcodes.ALOAD, 0); - m.visitFieldInsn(Opcodes.GETFIELD, "Example", "x", + m.visitFieldInsn(Opcodes.GETFIELD, "LateinitStringPrivate", "member", "Ljava/lang/String;"); m.visitVarInsn(Opcodes.ASTORE, 1); m.visitVarInsn(Opcodes.ALOAD, 1); m.visitJumpInsn(Opcodes.IFNONNULL, label); final AbstractInsnNode expectedFrom = m.instructions.getLast(); - m.visitLdcInsn("x"); + m.visitLdcInsn("member"); m.visitMethodInsn(Opcodes.INVOKESTATIC, "kotlin/jvm/internal/Intrinsics", "throwUninitializedPropertyAccessException", "(Ljava/lang/String;)V", false); m.visitInsn(Opcodes.ACONST_NULL); m.visitInsn(Opcodes.ATHROW); - final AbstractInsnNode expectedTo = m.instructions.getLast(); m.visitLabel(label); + final AbstractInsnNode expectedTo = m.instructions.getLast(); m.visitVarInsn(Opcodes.ALOAD, 1); m.visitInsn(Opcodes.ARETURN); @@ -98,18 +99,18 @@ public void should_filter_Kotlin_1_5() { /** *
-	 * class Example {
-	 *   lateinit var x: String
+	 * class LateinitStringPublic {
+	 *     lateinit var member: String
 	 * }
 	 * 
*/ @Test public void should_filter_Kotlin_1_5_public() { final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0, - "example", "()Ljava/lang/String;", null, null); + "getMember", "()Ljava/lang/String;", null, null); Label label = new Label(); m.visitVarInsn(Opcodes.ALOAD, 0); - m.visitFieldInsn(Opcodes.GETFIELD, "Example", "x", + m.visitFieldInsn(Opcodes.GETFIELD, "LateinitStringPublic", "member", "Ljava/lang/String;"); m.visitVarInsn(Opcodes.ASTORE, 1); m.visitVarInsn(Opcodes.ALOAD, 1); @@ -117,7 +118,8 @@ public void should_filter_Kotlin_1_5_public() { final AbstractInsnNode expectedFrom = m.instructions.getLast(); m.visitVarInsn(Opcodes.ALOAD, 1); m.visitInsn(Opcodes.ARETURN); - m.visitLdcInsn("x"); + m.visitLabel(label); + m.visitLdcInsn("member"); m.visitMethodInsn(Opcodes.INVOKESTATIC, "kotlin/jvm/internal/Intrinsics", "throwUninitializedPropertyAccessException", @@ -125,7 +127,6 @@ public void should_filter_Kotlin_1_5_public() { m.visitInsn(Opcodes.ACONST_NULL); m.visitInsn(Opcodes.ATHROW); final AbstractInsnNode expectedTo = m.instructions.getLast(); - m.visitLabel(label); filter.filter(m, context, output); @@ -134,34 +135,37 @@ public void should_filter_Kotlin_1_5_public() { /** *
-	 * class Example {
-	 *   private lateinit var x: String
-	 *   fun example() = x
+	 * class LateinitStringPrivate {
+	 *     private lateinit var member: String
+	 *
+	 *     fun get(): String = member
 	 * }
 	 * 
*/ @Test - public void should_filter_Kotlin_1_5_30() { + public void should_filter_Kotlin_1_5_30_private() { final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0, - "example", "()Ljava/lang/String;", null, null); - Label label = new Label(); + "get", "()Ljava/lang/String;", null, null); + Label l1 = new Label(); + Label l2 = new Label(); m.visitVarInsn(Opcodes.ALOAD, 0); - m.visitFieldInsn(Opcodes.GETFIELD, "Example", "x", + m.visitFieldInsn(Opcodes.GETFIELD, "LateinitStringPrivate", "member", "Ljava/lang/String;"); m.visitVarInsn(Opcodes.ASTORE, 1); m.visitVarInsn(Opcodes.ALOAD, 1); - m.visitJumpInsn(Opcodes.IFNONNULL, label); + m.visitJumpInsn(Opcodes.IFNONNULL, l1); final AbstractInsnNode expectedFrom = m.instructions.getLast(); - m.visitLdcInsn("x"); + m.visitLdcInsn("member"); m.visitMethodInsn(Opcodes.INVOKESTATIC, "kotlin/jvm/internal/Intrinsics", "throwUninitializedPropertyAccessException", "(Ljava/lang/String;)V", false); m.visitInsn(Opcodes.ACONST_NULL); - m.visitJumpInsn(Opcodes.GOTO, label); + m.visitJumpInsn(Opcodes.GOTO, l2); + m.visitLabel(l1); final AbstractInsnNode expectedTo = m.instructions.getLast(); - m.visitLabel(label); m.visitVarInsn(Opcodes.ALOAD, 1); + m.visitLabel(l2); m.visitInsn(Opcodes.ARETURN); filter.filter(m, context, output); @@ -171,18 +175,18 @@ public void should_filter_Kotlin_1_5_30() { /** *
-	 * class Example {
-	 *   lateinit var x: String
+	 * class LateinitStringPublic {
+	 *     lateinit var member: String
 	 * }
 	 * 
*/ @Test public void should_filter_Kotlin_1_5_30_public() { final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0, - "example", "()Ljava/lang/String;", null, null); + "getMember", "()Ljava/lang/String;", null, null); Label label = new Label(); m.visitVarInsn(Opcodes.ALOAD, 0); - m.visitFieldInsn(Opcodes.GETFIELD, "Example", "x", + m.visitFieldInsn(Opcodes.GETFIELD, "LateinitStringPublic", "member", "Ljava/lang/String;"); m.visitVarInsn(Opcodes.ASTORE, 1); m.visitVarInsn(Opcodes.ALOAD, 1); @@ -190,7 +194,8 @@ public void should_filter_Kotlin_1_5_30_public() { final AbstractInsnNode expectedFrom = m.instructions.getLast(); m.visitVarInsn(Opcodes.ALOAD, 1); m.visitInsn(Opcodes.ARETURN); - m.visitLdcInsn("x"); + m.visitLabel(label); + m.visitLdcInsn("member"); m.visitMethodInsn(Opcodes.INVOKESTATIC, "kotlin/jvm/internal/Intrinsics", "throwUninitializedPropertyAccessException", @@ -198,7 +203,6 @@ public void should_filter_Kotlin_1_5_30_public() { m.visitInsn(Opcodes.ACONST_NULL); m.visitInsn(Opcodes.ARETURN); final AbstractInsnNode expectedTo = m.instructions.getLast(); - m.visitLabel(label); filter.filter(m, context, output); @@ -207,35 +211,111 @@ public void should_filter_Kotlin_1_5_30_public() { /** *
-	 * class Example {
-	 *   private lateinit var x: T
-	 *   fun example() = x
+	 * class LateinitGenericPrivate {
+	 *     private lateinit var member: T
+	 *
+	 *     fun get(): T = member
 	 * }
 	 * 
*/ @Test - public void should_filter_Kotlin_1_5_30_generics() { + public void should_filter_Kotlin_1_5_30_private_generic() { final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0, - "example", "()Ljava/lang/Object;", null, null); + "get", "()Ljava/lang/String;", null, null); + Label l1 = new Label(); + Label l2 = new Label(); + m.visitVarInsn(Opcodes.ALOAD, 0); + m.visitFieldInsn(Opcodes.GETFIELD, "LateinitGenericPrivate", "member", + "Ljava/lang/Object;"); + m.visitVarInsn(Opcodes.ASTORE, 1); + m.visitVarInsn(Opcodes.ALOAD, 1); + m.visitJumpInsn(Opcodes.IFNONNULL, l1); + final AbstractInsnNode expectedFrom = m.instructions.getLast(); + m.visitLdcInsn("member"); + m.visitMethodInsn(Opcodes.INVOKESTATIC, + "kotlin/jvm/internal/Intrinsics", + "throwUninitializedPropertyAccessException", + "(Ljava/lang/String;)V", false); + m.visitFieldInsn(Opcodes.GETSTATIC, "kotlin/Unit", "INSTANCE", + "Lkotlin/Unit;"); + m.visitJumpInsn(Opcodes.GOTO, l2); + m.visitLabel(l1); + final AbstractInsnNode expectedTo = m.instructions.getLast(); + m.visitVarInsn(Opcodes.ALOAD, 1); + m.visitLabel(l2); + m.visitInsn(Opcodes.ARETURN); + + filter.filter(m, context, output); + + assertIgnored(new Range(expectedFrom, expectedTo)); + } + + /** + *
+	 * class LateinitGenericPublic {
+	 *     lateinit var member: T
+	 * }
+	 * 
+ */ + @Test + public void should_filter_Kotlin_1_5_30_public_generic() { + final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0, + "getMember", "()Ljava/lang/String;", null, null); Label label = new Label(); m.visitVarInsn(Opcodes.ALOAD, 0); - m.visitFieldInsn(Opcodes.GETFIELD, "Example", "x", + m.visitFieldInsn(Opcodes.GETFIELD, "LateinitGenericPublic", "member", "Ljava/lang/Object;"); m.visitVarInsn(Opcodes.ASTORE, 1); m.visitVarInsn(Opcodes.ALOAD, 1); - m.visitJumpInsn(Opcodes.IFNONNULL, label); + m.visitJumpInsn(Opcodes.IFNULL, label); final AbstractInsnNode expectedFrom = m.instructions.getLast(); - m.visitLdcInsn("x"); + m.visitVarInsn(Opcodes.ALOAD, 1); + m.visitInsn(Opcodes.ARETURN); + m.visitLabel(label); + m.visitLdcInsn("member"); m.visitMethodInsn(Opcodes.INVOKESTATIC, "kotlin/jvm/internal/Intrinsics", "throwUninitializedPropertyAccessException", "(Ljava/lang/String;)V", false); m.visitFieldInsn(Opcodes.GETSTATIC, "kotlin/Unit", "INSTANCE", "Lkotlin/Unit;"); - m.visitJumpInsn(Opcodes.GOTO, label); + m.visitInsn(Opcodes.ARETURN); final AbstractInsnNode expectedTo = m.instructions.getLast(); + + filter.filter(m, context, output); + + assertIgnored(new Range(expectedFrom, expectedTo)); + } + + /** + *
+	 * class LateinitStringPrivate {
+	 *     private lateinit var member: String
+	 *
+	 *     fun get(): String = member
+	 * }
+	 * 
+ */ + @Test + public void should_filter_Kotlin_1_6_private() { + final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0, + "get", "()Ljava/lang/String;", null, null); + Label label = new Label(); + m.visitVarInsn(Opcodes.ALOAD, 0); + m.visitFieldInsn(Opcodes.GETFIELD, "LateinitStringPrivate", "member", + "Ljava/lang/String;"); + m.visitInsn(Opcodes.DUP); + m.visitJumpInsn(Opcodes.IFNONNULL, label); + final AbstractInsnNode expectedFrom = m.instructions.getLast(); + m.visitInsn(Opcodes.POP); + m.visitLdcInsn("member"); + m.visitMethodInsn(Opcodes.INVOKESTATIC, + "kotlin/jvm/internal/Intrinsics", + "throwUninitializedPropertyAccessException", + "(Ljava/lang/String;)V", false); + m.visitInsn(Opcodes.ACONST_NULL); m.visitLabel(label); - m.visitVarInsn(Opcodes.ALOAD, 1); + final AbstractInsnNode expectedTo = m.instructions.getLast(); m.visitInsn(Opcodes.ARETURN); filter.filter(m, context, output); @@ -243,4 +323,40 @@ public void should_filter_Kotlin_1_5_30_generics() { assertIgnored(new Range(expectedFrom, expectedTo)); } + /** + *
+	 * class LateinitGenericPrivate {
+	 *     private lateinit var member: T
+	 *
+	 *     fun get(): T = member
+	 * }
+	 * 
+ */ + @Test + public void should_filter_Kotlin_1_6_private_generic() { + final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0, + "get", "()Ljava/lang/String;", null, null); + Label label = new Label(); + m.visitVarInsn(Opcodes.ALOAD, 0); + m.visitFieldInsn(Opcodes.GETFIELD, "LateinitGenericPrivate", "member", + "Ljava/lang/Object;"); + m.visitInsn(Opcodes.DUP); + m.visitJumpInsn(Opcodes.IFNONNULL, label); + final AbstractInsnNode expectedFrom = m.instructions.getLast(); + m.visitInsn(Opcodes.POP); + m.visitLdcInsn("member"); + m.visitMethodInsn(Opcodes.INVOKESTATIC, + "kotlin/jvm/internal/Intrinsics", + "throwUninitializedPropertyAccessException", + "(Ljava/lang/String;)V", false); + m.visitFieldInsn(Opcodes.GETSTATIC, "kotlin/Unit", "INSTANCE", + "Lkotlin/Unit;"); + m.visitLabel(label); + final AbstractInsnNode expectedTo = m.instructions.getLast(); + m.visitInsn(Opcodes.ARETURN); + + filter.filter(m, context, output); + + assertIgnored(new Range(expectedFrom, expectedTo)); + } } diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinLateinitFilter.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinLateinitFilter.java index 56be21edf3..ae0cfd52f1 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinLateinitFilter.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinLateinitFilter.java @@ -46,8 +46,20 @@ public AbstractInsnNode match(final AbstractInsnNode start) { cursor = start; if (Opcodes.IFNULL == start.getOpcode()) { - nextIs(Opcodes.ALOAD); - nextIs(Opcodes.ARETURN); + // we're looking for the + // throwUninitializedPropertyAccessException instruction, which + // is in the "null" branch, so we have to follow this jump. If + // we have an IFNONNULL instruction, we are already in die + // "null" branch and don't have to jump. + cursor = ((JumpInsnNode) start).label; + } + + AbstractInsnNode optionalPop = cursor.getNext(); + if (optionalPop != null && optionalPop.getOpcode() == Opcodes.POP) { + // Kotlin 1.6.0 DUPs the lateinit variable and POPs it here, + // previous versions instead load the variable twice. To be + // compatible with both, we can just skip the POP. + next(); } nextIs(Opcodes.LDC); @@ -55,51 +67,26 @@ public AbstractInsnNode match(final AbstractInsnNode start) { "throwUninitializedPropertyAccessException", "(Ljava/lang/String;)V"); - if (cursor != null - && skipNonOpcodes(cursor.getNext()) != skipNonOpcodes( - ((JumpInsnNode) start).label)) { - - final AbstractInsnNode node = cursor; - if (isKotlin1_5(node) || isKotlin1_5_30(node)) { - return cursor; - } + if (cursor == null) { return null; } - return cursor; - } - private boolean isKotlin1_5(AbstractInsnNode node) { - cursor = node; - nextIs(Opcodes.ACONST_NULL); - nextIs(Opcodes.ATHROW); - return cursor != null; - } - - private boolean isKotlin1_5_30(AbstractInsnNode node) { - cursor = node; - if (cursor.getNext() != null) { - switch (cursor.getNext().getOpcode()) { - case Opcodes.GETSTATIC: - nextIsField(Opcodes.GETSTATIC, "kotlin/Unit", "INSTANCE", - "Lkotlin/Unit;"); - break; - case Opcodes.ACONST_NULL: - nextIs(Opcodes.ACONST_NULL); - break; + if (Opcodes.IFNONNULL == start.getOpcode()) { + // ignore everything until the jump target of our IFNONNULL. + return ((JumpInsnNode) start).label; + } else { + // ignore everything until the next ARETURN or ATHROW + // instruction; or until the end of the function. + while (cursor.getOpcode() != Opcodes.ATHROW + && cursor.getOpcode() != Opcodes.ARETURN) { + AbstractInsnNode next = cursor.getNext(); + if (next == null) { + break; + } + cursor = next; } + return cursor; } - if (cursor.getNext() != null) { - switch (cursor.getNext().getOpcode()) { - case Opcodes.GOTO: - nextIs(Opcodes.GOTO); - break; - case Opcodes.ARETURN: - nextIs(Opcodes.ARETURN); - break; - } - } - return cursor != null; } - } } From ae4137baa9cba2b013ce6840cb2080f5e48c015b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20R=C3=B6ssler?= Date: Sun, 18 Dec 2022 00:17:21 +0100 Subject: [PATCH 5/5] Update the LateInitFilter for Kotlin 1.7.21 --- .../filter/KotlinLateinitFilterTest.java | 39 +++++++++++++++++++ .../analysis/filter/KotlinLateinitFilter.java | 7 ++++ 2 files changed, 46 insertions(+) diff --git a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/KotlinLateinitFilterTest.java b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/KotlinLateinitFilterTest.java index 790801d45f..7cc70fe5e0 100644 --- a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/KotlinLateinitFilterTest.java +++ b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/KotlinLateinitFilterTest.java @@ -359,4 +359,43 @@ public void should_filter_Kotlin_1_6_private_generic() { assertIgnored(new Range(expectedFrom, expectedTo)); } + + /** + *
+	 * class LateinitStringPublic {
+	 *     lateinit var member: String
+	 * }
+	 * 
+ * + * Kotlin 1.7.21 contains an additional frame node before the option pop. + */ + @Test + public void should_filter_Kotlin_1_7_21() { + final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0, + "get", "()Ljava/lang/String;", null, null); + Label label = new Label(); + m.visitVarInsn(Opcodes.ALOAD, 0); + m.visitFieldInsn(Opcodes.GETFIELD, "LateinitStringPublic", "member", + "Ljava/lang/String;"); + m.visitInsn(Opcodes.DUP); + m.visitJumpInsn(Opcodes.IFNULL, label); + final AbstractInsnNode expectedFrom = m.instructions.getLast(); + m.visitInsn(Opcodes.ARETURN); + m.visitLabel(label); + m.visitFrame(Opcodes.F_SAME1, 0, new Object[] {}, 1, + new String[] { "java/lang/String" }); + m.visitInsn(Opcodes.POP); + m.visitLdcInsn("member"); + m.visitMethodInsn(Opcodes.INVOKESTATIC, + "kotlin/jvm/internal/Intrinsics", + "throwUninitializedPropertyAccessException", + "(Ljava/lang/String;)V", false); + m.visitInsn(Opcodes.ACONST_NULL); + m.visitInsn(Opcodes.ARETURN); + final AbstractInsnNode expectedTo = m.instructions.getLast(); + + filter.filter(m, context, output); + + assertIgnored(new Range(expectedFrom, expectedTo)); + } } diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinLateinitFilter.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinLateinitFilter.java index 7672087ebd..3000c7e911 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinLateinitFilter.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinLateinitFilter.java @@ -14,6 +14,7 @@ import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.FrameNode; import org.objectweb.asm.tree.JumpInsnNode; import org.objectweb.asm.tree.MethodNode; @@ -54,6 +55,12 @@ public AbstractInsnNode match(final AbstractInsnNode start) { cursor = ((JumpInsnNode) start).label; } + AbstractInsnNode optionalFrame = cursor.getNext(); + if (optionalFrame != null && optionalFrame instanceof FrameNode + && ((FrameNode) optionalFrame).type == Opcodes.F_SAME1) { + next(); + } + AbstractInsnNode optionalPop = cursor.getNext(); if (optionalPop != null && optionalPop.getOpcode() == Opcodes.POP) { // Kotlin 1.6.0 DUPs the lateinit variable and POPs it here,