Skip to content
This repository has been archived by the owner on Jan 13, 2025. It is now read-only.

Lateinit unreachable code #5

Open
wants to merge 9 commits into
base: master
Choose a base branch
from

Conversation

ChristopherKoellner
Copy link

JaCoCo is reporting missing coverage for Kotlin (v1.5.30) lateinit properties.

The Kotlin compiler generates an uncover-able branch for lateinit properties. The compiler behaves differently given the following examples:

Example 1.

import kotlinx.coroutines.Job

class Test {

    private lateinit var job: Job

    fun test(): Job = job
}

which generates the following:

public final kotlinx.coroutines.Job test();
0: aload_0
1: getfield      #17                 // Field job:Lkotlinx/coroutines/Job;
4: astore_1
5: aload_1
6: ifnonnull     18
9: ldc           #18                 // String job
11: invokestatic  #24                // Method kotlin/jvm/internal/Intrinsics.throwUninitializedPropertyAccessException:(Ljava/lang/String;)V
14: aconst_null                      // <-- uncovered
15: goto          19                 // <-- uncovered
18: aload_1
19: areturn

Example 2.

class Test {
    class Simple
    lateinit var simple: Simple

    fun test(): Simple = simple
}

which generates the following:

public final com.mysugr.Simple test();
0: aload_0
1: invokevirtual #37                // Method getSimple:()Lcom/mysugr/Simple;
4: areturn

public final com.mysugr.Simple getSimple();
0: aload_0
1: getfield      #17                 
4: astore_1
5: aload_1
6: ifnull        11
9: aload_1
10: areturn
11: ldc           #18                 
13: invokestatic  #24                 // Method kotlin/jvm/internal/Intrinsics.throwUninitializedPropertyAccessException:(Ljava/lang/String;)V
16: aconst_null                       // <-- uncovered
17: areturn

Example 3.

class Test<T : Any> {
    private lateinit var instance: T

    fun test(): T = instance
}
0: aload_0
1: getfield      #19                 // Field instance:Ljava/lang/Object;
4: astore_1
5: aload_1
6: ifnonnull     20
9: ldc           #20                 // String instance
11: invokestatic  #26                 // Method kotlin/jvm/internal/Intrinsics.throwUninitializedPropertyAccessException:(Ljava/lang/String;)V
14: getstatic     #32                 // Field kotlin/Unit.INSTANCE:Lkotlin/Unit; <-- uncovered
17: goto          21
20: aload_1
21: areturn

In this PR the lateinit filter is extended to also filter the scenarios described.

@poseidon-mysugr poseidon-mysugr changed the base branch from myMaster to master November 17, 2021 14:12
@poseidon-mysugr
Copy link

Rework to be compatible with all variants from 1.4.0 to 1.6.0. The different versions of the Kotlin compiler generate slightly different versions of the check that leads to throwing an UninitializedPropertyAccessException (> mark the instructions we want to filter):

1.4.0

Doesn't differentiate, always produces the same byte code:

         0: aload_0
         1: getfield      #14                 // Field member:Ljava/lang/Object;
         4: dup
>        5: ifnonnull     13
>        8: ldc           #15                 // String member
>       10: invokestatic  #21                 // Method kotlin/jvm/internal/Intrinsics.throwUninitializedPropertyAccessException:(Ljava/lang/String;)V
        13: areturn

1.5.0

differentiates between private and public lateinit properties:

  • private:
         0: aload_0
         1: getfield      #17                 // Field member:Ljava/lang/String;
         4: astore_1
         5: aload_1
>        6: ifnonnull     16
>        9: ldc           #18                 // String member
>       11: invokestatic  #24                 // Method kotlin/jvm/internal/Intrinsics.throwUninitializedPropertyAccessException:(Ljava/lang/String;)V
>       14: aconst_null
>       15: athrow
        16: aload_1
        17: areturn
  • public:
         0: aload_0
         1: getfield      #17                 // Field member:Ljava/lang/String;
         4: astore_1
         5: aload_1
>        6: ifnull        11
>        9: aload_1
>       10: areturn
>       11: ldc           #18                 // String member
>       13: invokestatic  #24                 // Method kotlin/jvm/internal/Intrinsics.throwUninitializedPropertyAccessException:(Ljava/lang/String;)V
>       16: aconst_null
>       17: athrow

1.5.30

differentiates between private and public, and between generic and non-generic lateinit properties

  • private/generic:
         0: aload_0
         1: getfield      #19                 // Field member:Ljava/lang/Object;
         4: astore_1
         5: aload_1
>        6: ifnonnull     20
>        9: ldc           #20                 // String member
>       11: invokestatic  #26                 // Method kotlin/jvm/internal/Intrinsics.throwUninitializedPropertyAccessException:(Ljava/lang/String;)V
>       14: getstatic     #32                 // Field kotlin/Unit.INSTANCE:Lkotlin/Unit;
>       17: goto          21
        20: aload_1
        21: areturn
  • private/not generic:
         0: aload_0
         1: getfield      #17                 // Field member:Lcom/mysugr/LateinitSimplePrivate$Simple;
         4: astore_1
         5: aload_1
>        6: ifnonnull     18
>        9: ldc           #18                 // String member
>       11: invokestatic  #24                 // Method kotlin/jvm/internal/Intrinsics.throwUninitializedPropertyAccessException:(Ljava/lang/String;)V
>       14: aconst_null
>       15: goto          19
        18: aload_1
        19: areturn
  • public/generic:
         0: aload_0
         1: getfield      #19                 // Field member:Ljava/lang/Object;
         4: astore_1
         5: aload_1
>        6: ifnull        11
>        9: aload_1
>       10: areturn
>       11: ldc           #20                 // String member
>       13: invokestatic  #26                 // Method kotlin/jvm/internal/Intrinsics.throwUninitializedPropertyAccessException:(Ljava/lang/String;)V
>       16: getstatic     #32                 // Field kotlin/Unit.INSTANCE:Lkotlin/Unit;
>       19: areturn
  • public/not generic:
         0: aload_0
         1: getfield      #17                 // Field member:Lcom/mysugr/LateinitSimplePublic$Simple;
         4: astore_1
         5: aload_1
>        6: ifnull        11
>        9: aload_1
>       10: areturn
>       11: ldc           #18                 // String member
>       13: invokestatic  #24                 // Method kotlin/jvm/internal/Intrinsics.throwUninitializedPropertyAccessException:(Ljava/lang/String;)V
>       16: aconst_null
>       17: areturn

1.6.0

differentiates between private and public, and between generic and non-generic lateinit properties (like 1.5.30), but changed the byte code for the private variants

  • private/generic:
         0: aload_0
         1: getfield      #19                 // Field member:Ljava/lang/Object;
         4: dup
>        5: ifnonnull     17
>        8: pop
>        9: ldc           #20                 // String member
>       11: invokestatic  #26                 // Method kotlin/jvm/internal/Intrinsics.throwUninitializedPropertyAccessException:(Ljava/lang/String;)V
>       14: getstatic     #32                 // Field kotlin/Unit.INSTANCE:Lkotlin/Unit;
        17: areturn
  • private/not generic:
         0: aload_0
         1: getfield      #17                 // Field member:Lcom/mysugr/LateinitSimplePrivate$Simple;
         4: dup
>        5: ifnonnull     15
>        8: pop
>        9: ldc           #18                 // String member
>       11: invokestatic  #24                 // Method kotlin/jvm/internal/Intrinsics.throwUninitializedPropertyAccessException:(Ljava/lang/String;)V
>       14: aconst_null
        15: areturn
  • public/generic: same as 1.5.30
  • public/not generic: same as 1.5.30

It can be expected, that later versions of Kotlin 1.6 will apply the same dup/pop optimisation also for the public variants; the proposed filter should in this case also detect these cases just fine.

@poseidon-mysugr
Copy link

poseidon-mysugr commented Dec 18, 2022

1.7.21

Adds a new Frame instruction to all variants:

         0: aload_0
         1: getfield      #19                 // Field member:Ljava/lang/Object;
         4: dup
>        5: ifnonnull     17
>        6: frame         SAME1
>        8: pop
>        9: ldc           #20                 // String member
>       11: invokestatic  #26                 // Method kotlin/jvm/internal/Intrinsics.throwUninitializedPropertyAccessException:(Ljava/lang/String;)V
>       14: getstatic     #32                 // Field kotlin/Unit.INSTANCE:Lkotlin/Unit;
        17: areturn

@poseidon-mysugr poseidon-mysugr self-requested a review December 18, 2022 12:29
Copy link
Author

@ChristopherKoellner ChristopherKoellner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

# for free to subscribe to this conversation on GitHub. Already have an account? #.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants