How are "AnnotationRetention.BINARY" annotations represented in bycode?

I’m trying to figure out how annotations with “AnnotationRetention.BINARY” retention are represented in bytecode.

Annotations with “AnnotationRetention.RUNTIME” retention are explicitly added to bytecode.

Annotations with “AnnotationRetention.BINARY” retention policy are not explicitly added to bytecode. However, it seems like some tools can read them from bytecode.

E. g., “androidx.annotation.RequiresApi” annotation has “AnnotationRetention.BINARY” retention. It is not seen in bytecode. However, it seems that tools like Facebook’s RedEx can somehow read “RequiresApi” annotation.

UPD: Here is an example of the class:

class Test {
    companion object {
        @JvmStatic
        val testProp : Int = 0

        @RequiresApi(24)
        val testProp22 : Int = 0
    }
}

@JvmStatic is AnnotationRetention.RUNTIME
@RequiresApi is AnnotationRetention.BINARY

For “@JvmStatic” I see the following in Android Studio dex byte code viewer:

.method public static synthetic getTestProp$annotations()V
    .registers 0
    .annotation runtime Lkotlin/jvm/JvmStatic;
    .end annotation

    return-void
.end method

However, there is nothing for @RequiresApi annotation.

I also tried Konloch bytecode-viewer and the result is the same.

I don’t know the answer, but I would check the Metadata annotation to the enclosing class. Kotlin puts some internal stuff there.

edit:
Actually, did you check they are not present in the bytecode? I ask because the most obvious explanation would be that they are the same as Java’s RetentionPolicy.CLASS - so they are stored in the bytecode, but ignored by JVM at runtime.

@broot, thanks for you comment. Metadata was my guess too, but unfortunately I didn’t find any tool to “decode” metadata and prove that the annotation is somewhere there.

Actually, did you check they are not present in the bytecode?

I updated my post. I tried different viewers to check the bytecode.

Frankly speaking, I don’t think it has anything to do with Kotlin. It is rather Android. In the JVM bytecode both annotations are clearly present:

@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class RuntimeAnnotation

@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.BINARY)
annotation class BinaryAnnotation

object MyTest {
    @RuntimeAnnotation
    fun testRuntime() = Unit

    @BinaryAnnotation
    fun testBinary() = Unit
}
  public final void testRuntime();
    descriptor: ()V
    flags: (0x0011) ACC_PUBLIC, ACC_FINAL
    Code:
      stack=0, locals=1, args_size=1
         0: return
      LineNumberTable:
        line 41: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       1     0  this   LMyTest;
    RuntimeVisibleAnnotations:
      0: #12()
        RuntimeAnnotation

  public final void testBinary();
    descriptor: ()V
    flags: (0x0011) ACC_PUBLIC, ACC_FINAL
    Code:
      stack=0, locals=1, args_size=1
         0: return
      LineNumberTable:
        line 44: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       1     0  this   LMyTest;
    RuntimeInvisibleAnnotations:
      0: #14()
        BinaryAnnotation

I suspect runtime invisible annotations may be somehow separated or removed in the resulting dex, most probably in order to improve the performance.

2 Likes