Hi all,
I’ve just started a new project in Kotlin and I’m trying to integrate Cobertura with it. However, I’ve been running into some issues that seem to be caused by the code the Kotlin compiler is generating. Here is a simple method which exhibits the behavior:
fun trimIt(s: String): String {
return s.trim()
}
Compiling this function results in the following bytecode:
public static final java.lang.String trimIt(java.lang.String);
descriptor: (Ljava/lang/String;)Ljava/lang/String;
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
Code:
stack=4, locals=2, args_size=1
0: aload_0
1: astore_1
2: nop
3: aload_1
4: dup
5: ifnonnull 18
8: new #9 // class kotlin/TypeCastException
11: dup
12: ldc #11 // String null cannot be cast to non-null type kotlin.CharSequence
14: invokespecial #15 // Method kotlin/TypeCastException."<init>":(Ljava/lang/String;)V
17: athrow
18: checkcast #17 // class java/lang/CharSequence
21: invokestatic #23 // Method kotlin/text/StringsKt.trim:(Ljava/lang/CharSequence;)Ljava/lang/CharSequence;
24: invokevirtual #27 // Method java/lang/Object.toString:()Ljava/lang/String;
27: areturn
Between 4 and 17 is a generated branch that performs a null check (you can see the code which generates it here). This is what the bytecode looks like after Cobertura has instrumented it:
public static final java.lang.String trimIt(java.lang.String);
descriptor: (Ljava/lang/String;)Ljava/lang/String;
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
Code:
stack=5, locals=3, args_size=1
0: invokestatic #27 // Method __cobertura_init:()V
3: ldc #12 // int 0
5: istore_1
6: aload_0
7: astore_2
8: nop
9: aload_2
10: dup
11: ldc #14 // int 2
13: istore_1
14: ifnonnull 41
17: getstatic #29 // Field __cobertura_counters:[I
20: ldc #10 // int 1
22: dup2
23: iaload
24: ldc #10 // int 1
26: iadd
27: iastore
28: ldc #12 // int 0
30: istore_1
31: new #31 // class kotlin/TypeCastException
34: dup
35: ldc #33 // String null cannot be cast to non-null type kotlin.CharSequence
37: invokespecial #37 // Method kotlin/TypeCastException."<init>":(Ljava/lang/String;)V
40: athrow
41: getstatic #29 // Field __cobertura_counters:[I
44: iload_1
45: dup2
46: iaload
47: ldc #10 // int 1
49: iadd
50: iastore
51: ldc #12 // int 0
53: istore_1
54: checkcast #39 // class java/lang/CharSequence
57: invokestatic #45 // Method kotlin/text/StringsKt.trim:(Ljava/lang/CharSequence;)Ljava/lang/CharSequence;
60: invokevirtual #49 // Method java/lang/Object.toString:()Ljava/lang/String;
63: areturn
The generated null check is also being instrumented. I can’t easily check for this branch because it is a type error in Kotlin to pass in null
to a function which expects a value of non-nullable type. I tried writing a test that calls trimIt(null!!)
, but this still doesn’t seem to work.
I also found this StackOverflow post which suggests passing in -Xno-param-assertions
and -Xno-call-assertions
, but turning these on seemed to have no effect on this particular piece of code.
The Run ... with Coverage
option in IntelliJ does report that all branches are covered, but I can’t find a way to execute this from the command line / Maven. Since I need to run this during CI, it would be nice if this was possible.
Does anyone know either A) how to fix the issue so that Cobertura stops reporting false negatives, or B) how to call the IntelliJ coverage analysis from the command line, assuming it produces an easily-readable report?