Why Kotlin decompiler generates null.INSTANCE


#1

am trying to understand few of the Kotlin features by checking how it looks in Java side.

So as an experiment, I tried with this:

val printKotlin = fun () {
    print("Hello Kotlin")
}

So the output for the above snippet is:

public final class FunAsVariableKt {
    private static final Function0 printKotlin;

    public static final Function0 getPrintKotlin() {
        return printKotlin;
    }

    static {
        printKotlin = (Function0)null.INSTANCE;
    }
}

How do I understand the static block of the above decompiled code? Why it is generating this non working code?


#2

I can’t explain the generated java code. I would suggest that you take a look at the generated bytecode.

Full Bytecode
public final class Test/TestKt {


  // access flags 0x19
  public final static main([Ljava/lang/String;)V
    @Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
   L0
    ALOAD 0
    LDC "args"
    INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull (Ljava/lang/Object;Ljava/lang/String;)V
   L1
    LINENUMBER 5 L1
    GETSTATIC Test/TestKt.printKotlin : Lkotlin/jvm/functions/Function0;
    INVOKEINTERFACE kotlin/jvm/functions/Function0.invoke ()Ljava/lang/Object;
    POP
   L2
    LINENUMBER 6 L2
    RETURN
   L3
    LOCALVARIABLE args [Ljava/lang/String; L0 L3 0
    MAXSTACK = 2
    MAXLOCALS = 1

  // access flags 0x1A
  // signature Lkotlin/jvm/functions/Function0<Lkotlin/Unit;>;
  // declaration: kotlin.jvm.functions.Function0<kotlin.Unit>
  private final static Lkotlin/jvm/functions/Function0; printKotlin
  @Lorg/jetbrains/annotations/NotNull;() // invisible

  // access flags 0x19
  // signature ()Lkotlin/jvm/functions/Function0<Lkotlin/Unit;>;
  // declaration: kotlin.jvm.functions.Function0<kotlin.Unit> getPrintKotlin()
  public final static getPrintKotlin()Lkotlin/jvm/functions/Function0;
  @Lorg/jetbrains/annotations/NotNull;() // invisible
   L0
    LINENUMBER 8 L0
    GETSTATIC Test/TestKt.printKotlin : Lkotlin/jvm/functions/Function0;
    ARETURN
   L1
    MAXSTACK = 1
    MAXLOCALS = 0

  // access flags 0x8
  static <clinit>()V
   L0
    LINENUMBER 8 L0
    GETSTATIC Test/TestKt$printKotlin$1.INSTANCE : LTest/TestKt$printKotlin$1;
    CHECKCAST kotlin/jvm/functions/Function0
    PUTSTATIC Test/TestKt.printKotlin : Lkotlin/jvm/functions/Function0;
    RETURN
    MAXSTACK = 1
    MAXLOCALS = 0

  @Lkotlin/Metadata;(mv={1, 1, 13}, bv={1, 0, 3}, k=2, d1={"\u0000\u001a\n\u0000\n\u0002\u0018\u0002\n\u0002\u0010\u0002\n\u0002\u0008\u0004\n\u0002\u0010\u0011\n\u0002\u0010\u000e\n\u0002\u0008\u0002\u001a\u0019\u0010\u0005\u001a\u00020\u00022\u000c\u0010\u0006\u001a\u0008\u0012\u0004\u0012\u00020\u00080\u0007\u00a2\u0006\u0002\u0010\u0009\"\u0017\u0010\u0000\u001a\u0008\u0012\u0004\u0012\u00020\u00020\u0001\u00a2\u0006\u0008\n\u0000\u001a\u0004\u0008\u0003\u0010\u0004\u00a8\u0006\n"}, d2={"printKotlin", "Lkotlin/Function0;", "", "getPrintKotlin", "()Lkotlin/jvm/functions/Function0;", "main", "args", "", "", "([Ljava/lang/String;)V", "Test"})
  // access flags 0x18
  final static INNERCLASS Test/TestKt$printKotlin$1 null null
  // compiled from: Test.kt
}


// ================Test/TestKt$printKotlin$1.class =================
// class version 50.0 (50)
// access flags 0x30
// signature Lkotlin/jvm/internal/Lambda;Lkotlin/jvm/functions/Function0<Lkotlin/Unit;>;
// declaration: Test/TestKt$printKotlin$1 extends kotlin.jvm.internal.Lambda implements kotlin.jvm.functions.Function0<kotlin.Unit>
final class Test/TestKt$printKotlin$1 extends kotlin/jvm/internal/Lambda  implements kotlin/jvm/functions/Function0  {


  // access flags 0x1041
  public synthetic bridge invoke()Ljava/lang/Object;
    ALOAD 0
    INVOKEVIRTUAL Test/TestKt$printKotlin$1.invoke ()V
    GETSTATIC kotlin/Unit.INSTANCE : Lkotlin/Unit;
    ARETURN
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x11
  public final invoke()V
   L0
    LINENUMBER 9 L0
    LDC "Hello Kotlin"
    ASTORE 1
   L1
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    ALOAD 1
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V
   L2
   L3
    LINENUMBER 10 L3
    RETURN
   L4
    LOCALVARIABLE this LTest/TestKt$printKotlin$1; L0 L4 0
    MAXSTACK = 2
    MAXLOCALS = 2

  // access flags 0x0
  <init>()V
    ALOAD 0
    ICONST_0
    INVOKESPECIAL kotlin/jvm/internal/Lambda.<init> (I)V
    RETURN
    MAXSTACK = 2
    MAXLOCALS = 1

  // access flags 0x19
  public final static LTest/TestKt$printKotlin$1; INSTANCE

  // access flags 0x8
  static <clinit>()V
    NEW Test/TestKt$printKotlin$1
    DUP
    INVOKESPECIAL Test/TestKt$printKotlin$1.<init> ()V
    PUTSTATIC Test/TestKt$printKotlin$1.INSTANCE : LTest/TestKt$printKotlin$1;
    RETURN
    MAXSTACK = 2
    MAXLOCALS = 0

  @Lkotlin/Metadata;(mv={1, 1, 13}, bv={1, 0, 3}, k=3, d1={"\u0000\u0008\n\u0000\n\u0002\u0010\u0002\n\u0000\u0010\u0000\u001a\u00020\u0001H\n\u00a2\u0006\u0002\u0008\u0002"}, d2={"<no name provided>", "", "invoke"})
  OUTERCLASS Test/TestKt null
  // access flags 0x18
  final static INNERCLASS Test/TestKt$printKotlin$1 null null
  // compiled from: Test.kt
  // debug info: SMAP
}

Let me try to explain the relevant parts.

/*val printKotlin = */ fun() {
    println("Hello Kotlin")
}

generates this part of the bytecode:

// ================Test/TestKt$printKotlin$1.class =================
// class version 50.0 (50)
// access flags 0x30
// signature Lkotlin/jvm/internal/Lambda;Lkotlin/jvm/functions/Function0<Lkotlin/Unit;>;
// declaration: Test/TestKt$printKotlin$1 extends kotlin.jvm.internal.Lambda implements kotlin.jvm.functions.Function0<kotlin.Unit>
final class Test/TestKt$printKotlin$1 extends kotlin/jvm/internal/Lambda  implements kotlin/jvm/functions/Function0  {


  // access flags 0x1041
  public synthetic bridge invoke()Ljava/lang/Object;
    ALOAD 0
    INVOKEVIRTUAL Test/TestKt$printKotlin$1.invoke ()V
    GETSTATIC kotlin/Unit.INSTANCE : Lkotlin/Unit;
    ARETURN
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x11
  public final invoke()V
   L0
    LINENUMBER 9 L0
    LDC "Hello Kotlin"
    ASTORE 1
   L1
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    ALOAD 1
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V
   L2
   L3
    LINENUMBER 10 L3
    RETURN
   L4
    LOCALVARIABLE this LTest/TestKt$printKotlin$1; L0 L4 0
    MAXSTACK = 2
    MAXLOCALS = 2

  // access flags 0x0
  <init>()V
    ALOAD 0
    ICONST_0
    INVOKESPECIAL kotlin/jvm/internal/Lambda.<init> (I)V
    RETURN
    MAXSTACK = 2
    MAXLOCALS = 1

  // access flags 0x19
  public final static LTest/TestKt$printKotlin$1; INSTANCE

  // access flags 0x8
  static <clinit>()V
    NEW Test/TestKt$printKotlin$1
    DUP
    INVOKESPECIAL Test/TestKt$printKotlin$1.<init> ()V
    PUTSTATIC Test/TestKt$printKotlin$1.INSTANCE : LTest/TestKt$printKotlin$1;  
    RETURN
    MAXSTACK = 2
    MAXLOCALS = 0

  @Lkotlin/Metadata;(mv={1, 1, 13}, bv={1, 0, 3}, k=3, d1={"\u0000\u0008\n\u0000\n\u0002\u0010\u0002\n\u0000\u0010\u0000\u001a\u00020\u0001H\n\u00a2\u0006\u0002\u0008\u0002"}, d2={"<no name provided>", "", "invoke"})

The function get’s transformed into a class, that way you can pass around instances of this Function (Kotlin treats all higher order functions this way). The code println("Hello Kotlin") can be found in this class invoke function.
Also of note is the final static INSTANCE field which get’s set in the static constructor to a new instance of this class.

The assignment of the function to val printKotlin = ... generates this code:

// access flags 0x1A
  // signature Lkotlin/jvm/functions/Function0<Lkotlin/Unit;>;
  // declaration: kotlin.jvm.functions.Function0<kotlin.Unit>
  private final static Lkotlin/jvm/functions/Function0; printKotlin
  @Lorg/jetbrains/annotations/NotNull;() // invisible

  // access flags 0x19
  // signature ()Lkotlin/jvm/functions/Function0<Lkotlin/Unit;>;
  // declaration: kotlin.jvm.functions.Function0<kotlin.Unit> getPrintKotlin()
  public final static getPrintKotlin()Lkotlin/jvm/functions/Function0;
  @Lorg/jetbrains/annotations/NotNull;() // invisible
   L0
    LINENUMBER 8 L0
    GETSTATIC Test/Test.printKotlin : Lkotlin/jvm/functions/Function0;
    ARETURN
   L1
    MAXSTACK = 1
    MAXLOCALS = 0

  // access flags 0x8
  static <clinit>()V
   L0
    LINENUMBER 8 L0
    GETSTATIC Test/Test$printKotlin$1.INSTANCE : LTest/Test$printKotlin$1;
    CHECKCAST kotlin/jvm/functions/Function0
    PUTSTATIC Test/Test.printKotlin : Lkotlin/jvm/functions/Function0;
    RETURN
    MAXSTACK = 1
    MAXLOCALS = 0

There are 3 parts to it. First it creates a private static field of type Function0 to store the reference to the class generated. Then it generates a getter, just like you are used for any property. The last part is in the static constructor. It takes a reference to printKotlin$1.INSTANCE and stores it in our private field.

So where does the null.INSTANCE come from? I guess that the java decompiler does not recognize the hidden class TestKt$printKotlin$1 and therefor uses null instead.

Another fact to note is what happens if you capture values inside of the higher order function. That way the compiler can no longer use the same instance of the function-class so it will create multiple instances, each saving the captured values.


Also take everything I explained above with a grain of salt. I’m in no way an expert on how kotlin works internally, nor on jvm-bytecode. In general my explanation should stand, but pls don’t ask me to explain every part of the bytecode, as I would need to guess myself :wink:
Hope this helped.