Skip to content

Add filter for instructions inlined by Kotlin compiler #763

@Godin

Description

@Godin

Consider following files

a.kt

inline fun a() {
  nop()
}

b.kt

inline fun b() {
  nop()
}

and callsite.kt with 8 lines

fun main(args : Array<String>) {
  a() // line 2
  b() // line 3
} // line 4

fun nop() {
} // line 7

Kotlin compiler produces CallsiteKt.class whose bytecode

Constant pool:
...
   #7 = Utf8               Lorg/jetbrains/annotations/NotNull;
...
  #24 = Utf8               Lkotlin/Metadata;
...
{
  public static final void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
    Code:
      stack=2, locals=2, args_size=1
         0: aload_0
         1: ldc           #9                  // String args
         3: invokestatic  #15                 // Method kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V
         6: nop
         7: invokestatic  #19                 // Method nop:()V
        10: nop
        11: nop
        12: invokestatic  #19                 // Method nop:()V
        15: nop
        16: return
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            7       4     1 $i$f$a   I
           12       4     1 $i$f$b   I
            0      17     0  args   [Ljava/lang/String;
      LineNumberTable:
        line 2: 6
        line 9: 7
        line 10: 10
        line 3: 11
        line 11: 12
        line 12: 15
        line 4: 16
    RuntimeInvisibleParameterAnnotations:
      0:
        0: #7()

  public static final void nop();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
    Code:
      stack=0, locals=0, args_size=0
         0: return
      LineNumberTable:
        line 7: 0
}
SourceFile: "callsite.kt"
SourceDebugExtension:
  SMAP
  callsite.kt
  Kotlin
  *S Kotlin
  *F
  + 1 callsite.kt
  CallsiteKt
  + 2 a.kt
  AKt
  + 3 b.kt
  BKt
  *L
  1#1,8:1
  2#2,2:9
  2#3,2:11
  *E
  *S KotlinDebug
  *F
  + 1 callsite.kt
  CallsiteKt
  *L
  2#1,2:9
  3#1,2:11
  *E
RuntimeVisibleAnnotations:
  0: #24(#25=[I#26,I#26,I#27],#28=[I#26,I#29,I#30],#31=I#30,#32=[s#33],#34=[s#5,s#35,s#8,s#35,s#35,s#6,s#16])

Contains inlined instructions with line numbers that are greater than actually present in source file:

$ java -jar jacoco-0.8.2/lib/jacococli.jar classinfo CallsiteKt.class
  INST   BRAN   LINE   METH   CXTY   ELEMENT
    11      0      8      2      2   class 0xcc96f697bc87baa7 CallsiteKt
    10      0      7      1      1   +- method main([Ljava/lang/String;)V
     1      0                        |  +- line 2
     1      0                        |  +- line 3
     1      0                        |  +- line 4
     1      0                        |  +- line 9
     1      0                        |  +- line 10
     1      0                        |  +- line 11
     1      0                        |  +- line 12
     1      0      1      1      1   +- method nop()V
     1      0                           +- line 7

Since we use source line numbers for generation of page with highlighted source in our HTML report, additional lines (9-12) do not appear on it and do not cause troubles. However they add to values of counters as shown by classinfo above and what is also visible in HTML report:

methods

sources

Not demonstrated by above example, but inlining can also affect "branches" and "complexity" counters.

Moreover these additional lines appear in XML report:

        <sourcefile name="callsite.kt">
            <line nr="2" mi="2" ci="0" mb="0" cb="0"/>
            <line nr="3" mi="1" ci="0" mb="0" cb="0"/>
            <line nr="4" mi="1" ci="0" mb="0" cb="0"/>
            <line nr="7" mi="1" ci="0" mb="0" cb="0"/>
            <line nr="9" mi="1" ci="0" mb="0" cb="0"/>
            <line nr="10" mi="1" ci="0" mb="0" cb="0"/>
            <line nr="12" mi="1" ci="0" mb="0" cb="0"/>
            <line nr="13" mi="1" ci="0" mb="0" cb="0"/>
            <line nr="14" mi="1" ci="0" mb="0" cb="0"/>
            <counter type="INSTRUCTION" missed="13" covered="0"/>
            <counter type="LINE" missed="9" covered="0"/>
            <counter type="COMPLEXITY" missed="2" covered="0"/>
            <counter type="METHOD" missed="2" covered="0"/>
            <counter type="CLASS" missed="1" covered="0"/>
        </sourcefile>

Which is not good for tools that read our XML report and try to enforce its consistency with source code by checking that line numbers are not greater than in source code.

While ideally we need to somehow map these additional instructions back to their original location in order to show coverage of original (#654).

I propose to do a baby step and at least ignore them at call sites. Seems that for that is enough to obtain minimal value of OutputStartLine in LineSection that comes after KotlinDebug StratumSection of SMAP recorded in class file - minimum of 9 and 11 in the above example:

  *S KotlinDebug
  *F
  + 1 callsite.kt
  CallsiteKt
  *L
  2#1,2:9
  3#1,2:11
  *E

Metadata

Metadata

Assignees

Type

No type

Projects

Status

Done

Status

Done

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions