Kotlinx.coroutines: kotlin.coroutines.jvm.internal.DebugMetadataKt.getDebugMetadataAnnotation(DebugMetadata.kt:1)

Created on 24 Nov 2018  路  34Comments  路  Source: Kotlin/kotlinx.coroutines

The app crash when I enabled R8 and proguard

android.enableR8 = true
android.enableR8.fullMode=true

proguard-rules

# ServiceLoader support
-keepnames class kotlinx.coroutines.internal.MainDispatcherFactory {}
-keepnames class kotlinx.coroutines.CoroutineExceptionHandler {}

# Most of volatile fields are updated with AFU and should not be mangled
-keepclassmembernames class kotlinx.** {
    volatile <fields>;
}

This issues most encountered on Android 4.4. 5x and 6.x. It hadn't this problem when I used the version 1.0.0 and enabled the only proguard.

Android studio 3.3-beta04
Kotlin 1.3.10
Coroutines 1.0.1

Fatal Exception: java.lang.IncompatibleClassChangeError: Couldn't find kotlin.c.b.a.f.i
       at libcore.reflect.AnnotationAccess.toAnnotationInstance(AnnotationAccess.java:659)
       at libcore.reflect.AnnotationAccess.toAnnotationInstance(AnnotationAccess.java:641)
       at libcore.reflect.AnnotationAccess.getDeclaredAnnotation(AnnotationAccess.java:170)
       at libcore.reflect.AnnotationAccess.getAnnotation(AnnotationAccess.java:72)
       at java.lang.Class.getAnnotation(Class.java:343)
       at kotlin.coroutines.jvm.internal.DebugMetadataKt.getDebugMetadataAnnotation(DebugMetadata.kt:1)
       at kotlin.coroutines.jvm.internal.DebugMetadataKt.getStackTraceElement(DebugMetadata.kt:1)
       at kotlin.coroutines.jvm.internal.BaseContinuationImpl.getStackTraceElement(ContinuationImpl.kt:13)
       at kotlin.coroutines.jvm.internal.BaseContinuationImpl.toString(ContinuationImpl.kt:1)
       at kotlin.coroutines.jvm.internal.SuspendLambda.toString(ContinuationImpl.kt:3)
       at java.lang.StringBuilder.append(StringBuilder.java:202)
       at kotlinx.coroutines.DebugKt.toDebugString(Debug.kt:3)
       at kotlinx.coroutines.DispatchedContinuation.toString(Dispatched.kt:1)
       at java.lang.StringBuilder.append(StringBuilder.java:202)
       at android.os.Looper.loop(Looper.java:160)
       at android.app.ActivityThread.main(ActivityThread.java:5550)
       at java.lang.reflect.Method.invoke(Method.java)
       at java.lang.reflect.Method.invoke(Method.java:372)
       at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:955)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:750)
Fatal Exception: java.lang.RuntimeException: failure in processEncodedAnnotation
       at java.lang.Class.getDeclaredAnnotation(Class.java)
       at java.lang.Class.getAnnotation(Class.java:290)
       at kotlin.coroutines.jvm.internal.DebugMetadataKt.getDebugMetadataAnnotation(DebugMetadata.kt:1)
       at kotlin.coroutines.jvm.internal.DebugMetadataKt.getStackTraceElement(DebugMetadata.kt:1)
       at kotlin.coroutines.jvm.internal.BaseContinuationImpl.getStackTraceElement(ContinuationImpl.kt:1)
       at kotlin.coroutines.jvm.internal.BaseContinuationImpl.toString(ContinuationImpl.kt:1)
       at kotlin.coroutines.jvm.internal.SuspendLambda.toString(ContinuationImpl.kt:3)
       at java.lang.StringBuilder.append(StringBuilder.java:202)
       at kotlinx.coroutines.DebugKt.toDebugString(Debug.kt:3)
       at kotlinx.coroutines.DispatchedContinuation.toString(Dispatched.kt:1)
       at java.lang.StringBuilder.append(StringBuilder.java:202)
       at android.os.Looper.loop(Looper.java:159)
       at android.app.ActivityThread.main(ActivityThread.java:5292)
       at java.lang.reflect.Method.invokeNative(Method.java)
       at java.lang.reflect.Method.invoke(Method.java:515)
       at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:824)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:640)
       at dalvik.system.NativeStart.main(NativeStart.java)
question

Most helpful comment

I've tried to reproduce this issue in R8's testing env. to see if we're missing what we should do: https://r8-review.googlesource.com/c/r8/+/32082, but then found a simple, self-contained reproduction that is failing even on JVM. Android legacy VMs have different logic to retrieve annotations, and thus we're seeing different errors. Anyhow, here is the reproducing steps:
coroutines-issue-858.zip

$ unzip coroutines-issue-858.zip
Archive:  coroutines-issue-858.zip
  inflating: internal_annotation/Annotation.kt  
  inflating: internal_annotation/Base.kt  
  inflating: internal_annotation/main.kt  
$ ~/projects/r8/third_party/kotlin/kotlinc/bin/kotlinc-jvm -version
info: kotlinc-jvm 1.2.30 (JRE 1.8.0_181-8u181-b13-2~deb9u1-b13)
$ ~/projects/r8/third_party/kotlin/kotlinc/bin/kotlinc-jvm internal_annotation/
$ java -cp ~/projects/r8/third_party/kotlin/kotlinc/lib/kotlin-stdlib.jar:. internal_annotation.MainKt
Impl::toString
Exception in thread "main" java.lang.NoSuchMethodError: internal_annotation.Annotation.f2()Ljava/lang/String;
    at internal_annotation.AnnotationKt.foo(Annotation.kt:17)
    at internal_annotation.MainKt.main(main.kt:9)

Not sure it's because of using that old kotlinc. Looking at generated bytecode:

$ javap -c -p internal_annotation/Annotation.class
Compiled from "Annotation.kt"
public interface internal_annotation.Annotation extends java.lang.annotation.Annotation {
  public abstract int field1();

  public abstract java.lang.String field2();
}
$ javap -c -p internal_annotation/AnnotationKt.class
Compiled from "Annotation.kt"
public final class internal_annotation.AnnotationKt {
  public static final java.lang.StackTraceElement foo(internal_annotation.Base);
    Code:
       0: aload_0
       1: ldc           #12                 // String $receiver
       3: invokestatic  #18                 // Method kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V
       6: aload_0
       7: invokestatic  #22                 // Method getMyAnnotation:(Linternal_annotation/Base;)Linternal_annotation/Annotation;
      10: dup
      11: ifnull        17
      14: goto          20
      17: pop
      18: aconst_null
      19: areturn
      20: astore_1
      21: new           #24                 // class java/lang/StackTraceElement
      24: dup
      25: aload_1
      26: invokeinterface #30,  1           // InterfaceMethod internal_annotation/Annotation.f2:()Ljava/lang/String;
      31: aload_1
      32: invokeinterface #30,  1           // InterfaceMethod internal_annotation/Annotation.f2:()Ljava/lang/String;
      37: aload_1
      38: invokeinterface #30,  1           // InterfaceMethod internal_annotation/Annotation.f2:()Ljava/lang/String;
      43: aload_1
      44: invokeinterface #34,  1           // InterfaceMethod internal_annotation/Annotation.f1:()I
      49: invokespecial #38                 // Method java/lang/StackTraceElement."<init>":(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)V
      52: areturn

  private static final internal_annotation.Annotation getMyAnnotation(internal_annotation.Base);
    Code:
       0: aload_0
       1: invokevirtual #45                 // Method java/lang/Object.getClass:()Ljava/lang/Class;
       4: ldc           #26                 // class internal_annotation/Annotation
       6: invokevirtual #51                 // Method java/lang/Class.getAnnotation:(Ljava/lang/Class;)Ljava/lang/annotation/Annotation;
       9: checkcast     #26                 // class internal_annotation/Annotation
      12: areturn
}

Indeed, there is no f2() in Annotation.class, while there are attempts to call it:

      26: invokeinterface #30,  1           // InterfaceMethod internal_annotation/Annotation.f2:()Ljava/lang/String;

So, the issue is, in the annotation definition:

$ cat internal_annotation/Annotation.kt
...
@Target(AnnotationTarget.CLASS)
internal annotation class Annotation(
    @get:JvmName("f1")
    val field1: Int = 0,
    @get:JvmName("f2")
    val field2: String = ""
)
...

there are implicit name mappings from field2 to f2 and the field access is replaced with the getter:

$ cat internal_annotation/Annotation.kt
...
@JvmName("foo")
internal fun Base.foo(): StackTraceElement? {
  val anno = getMyAnnotation() ?: return null
  return StackTraceElement(anno.field2, anno.field2, anno.field2, anno.field1) // <-- here f2() is not found.
}
...

Proguard/R8 can remedy this by using -applymapping. In my testing setting,

$ cat getter-mapping.txt
...
internal_annotation.Annotation -> internal_annotation.Annotation:
  int f1() -> field1
  java.lang.String f2() -> field2

and adding:

-applymapping getter-mapping.txt

to the proguard config replaced interface call to f2() with field2() and got the expected output:

Impl::toString
Impl::Annotation::field2.Impl::Annotation::field2(Impl::Annotation::field2:2)

So, all exception reports above that end with

Fatal Exception: java.lang.IncompatibleClassChangeError: Couldn't find kotlin.c.b.a.f.i

or with different package hierarhcy are due to missing getter that is defined at this line:
https://github.com/JetBrains/kotlin/blob/master/libraries/stdlib/jvm/src/kotlin/coroutines/jvm/internal/DebugMetadata.kt#L23

All 34 comments

As mentioned in announcement https://android-developers.googleblog.com/2018/11/r8-new-code-shrinker-from-google-is.html
Enabling R8 fullmode you are opt in into incompatible with proguard behaviour so R8 specific rule set needs to be provided. Proguard rules are not enough.

For the more adventurous, R8 also has full mode that is not directly compatible with Proguard.

Jake Wharton commented on slack, you can use both modes without a problem. Before I enabled R8 (not fullMode) with proguard in other apps but this bug still issues. Maybe the problem from the coroutines version 1.0.1, proguard wasn't consumed or missing rules.

Could you please provide a self-contained reproducer?
Taking into account a variety of androidx tools which all have different issues it is hard to point the bug to particular versions

Thank you. But I can't create a sample to help you test. It encountered on Android 5.x and 6.x but most Android ROM from Indian producers. This bug is very to fix when it depends on a group devices or some devices. But no problem if you can't fix, maybe we need to more reports about it and a sample. Thank team for hardworking!

java.lang.IncompatibleClassChangeError: 
  at libcore.reflect.AnnotationAccess.toAnnotationInstance (AnnotationAccess.java:659)
  at libcore.reflect.AnnotationAccess.toAnnotationInstance (AnnotationAccess.java:641)
  at libcore.reflect.AnnotationAccess.getDeclaredAnnotation (AnnotationAccess.java:170)
  at libcore.reflect.AnnotationAccess.getAnnotation (AnnotationAccess.java:72)
  at java.lang.Class.getAnnotation (Class.java:343)
  at kotlin.coroutines.jvm.internal.DebugMetadataKt.getDebugMetadataAnnotation (DebugMetadataKt.java:48)
  at kotlin.coroutines.jvm.internal.DebugMetadataKt.getStackTraceElement (DebugMetadataKt.java:40)
  at kotlin.coroutines.jvm.internal.BaseContinuationImpl.getStackTraceElement (BaseContinuationImpl.java:75)
  at kotlin.coroutines.jvm.internal.BaseContinuationImpl.toString (BaseContinuationImpl.java:67)
  at kotlin.coroutines.jvm.internal.SuspendLambda.toString (SuspendLambda.java:165)
  at java.lang.StringBuilder.append (StringBuilder.java:202)
  at kotlinx.coroutines.DebugKt.toDebugString (DebugKt.java:46)
  at kotlinx.coroutines.DispatchedContinuation.toString (DispatchedContinuation.java:183)
  at java.lang.StringBuilder.append (StringBuilder.java:202)
  at android.os.Looper.loop (Looper.java:155)
  at android.app.ActivityThread.main (ActivityThread.java:5529)
  at java.lang.reflect.Method.invoke (Method.java)
  at java.lang.reflect.Method.invoke (Method.java:372)
  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run (ZygoteInit.java:950)
  at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:745)

image

I see this crash with R8, but without the full mode. All Android 5 & 6 devices (minsdk 21).

image

Fatal Exception: java.lang.IncompatibleClassChangeError: Couldn't find g.c.b.a.f.i
       at libcore.reflect.AnnotationAccess.toAnnotationInstance(AnnotationAccess.java:659)
       at libcore.reflect.AnnotationAccess.toAnnotationInstance(AnnotationAccess.java:641)
       at libcore.reflect.AnnotationAccess.getDeclaredAnnotation(AnnotationAccess.java:170)
       at libcore.reflect.AnnotationAccess.getAnnotation(AnnotationAccess.java:72)
       at java.lang.Class.getAnnotation(Class.java:343)
       at kotlin.coroutines.jvm.internal.DebugMetadataKt.getDebugMetadataAnnotation(SourceFile:1)
       at kotlin.coroutines.jvm.internal.DebugMetadataKt.getStackTraceElement(SourceFile:1)
       at kotlin.coroutines.jvm.internal.BaseContinuationImpl.getStackTraceElement(SourceFile:1)
       at kotlin.coroutines.jvm.internal.BaseContinuationImpl.toString(SourceFile:1)
       at kotlin.coroutines.jvm.internal.SuspendLambda.toString(SourceFile:3)
       at java.lang.StringBuilder.append(StringBuilder.java:202)
       at kotlinx.coroutines.DebugKt.toDebugString(SourceFile:3)
       at kotlinx.coroutines.DispatchedContinuation.toString(SourceFile:1)
       at java.lang.StringBuilder.append(StringBuilder.java:202)
       at android.os.Looper.loop(Looper.java:155)
       at android.app.ActivityThread.main(ActivityThread.java:5529)
       at java.lang.reflect.Method.invoke(Method.java)
       at java.lang.reflect.Method.invoke(Method.java:372)
       at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:956)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:751)

Googling stacktrace leads to this https://issuetracker.google.com/issues/37045084 Can you confirm your code doesn't fit into single dex after shrinking? (there seem to be an issue with loading annotation if it doesn't end up not in main.dex file)

Yes we're multidexing.

We are not using multidexing and got the same crash on several android 5.0 mediatek devices.

Android studio 3.3-beta04
Kotlin 1.3.10
Coroutines 1.0.1
R8: yes
R8-full: no
Multidex: no

Crashing devices:
1) Vivo Y33
2) Alco 10 Viking Pro

Both are mediatek devices on android 5.

FatalException: java.lang.IncompatibleClassChangeError: Couldn't find vkb.i
at libcore.reflect.AnnotationAccess.toAnnotationInstance(AnnotationAccess.java:659)
at libcore.reflect.AnnotationAccess.toAnnotationInstance(AnnotationAccess.java:641)
at libcore.reflect.AnnotationAccess.getDeclaredAnnotation(AnnotationAccess.java:170)
at libcore.reflect.AnnotationAccess.getAnnotation(AnnotationAccess.java:72)
at java.lang.Class.getAnnotation(Class.java:343)10 Viking Pro
at kotlin.coroutines.jvm.internal.DebugMetadataKt.getDebugMetadataAnnotation(Unknown Source:1)
at kotlin.coroutines.jvm.internal.DebugMetadataKt.getStackTraceElement(Unknown Source:1)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.getStackTraceElement(Unknown Source:1)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.java.lang.String toString()(Unknown Source:1)
at kotlin.coroutines.jvm.internal.SuspendLambda.java.lang.String toString()(Unknown Source:3)
at java.lang.StringBuilder.append(StringBuilder.java:202)
at kotlinx.coroutines.DebugKt.toDebugString(Unknown Source:3)
at kotlinx.coroutines.DispatchedContinuation.java.lang.String toString()(Unknown Source:1)
at java.lang.StringBuilder.append(StringBuilder.java:202)
at android.os.Looper.loop(Looper.java:160)
at android.app.ActivityThread.main(ActivityThread.java:5576)
at java.lang.reflect.Method.invoke(Method.java)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:955)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:750)

My apps don't use multiple dex or use had this problem.

We have confirmed the issue to be D8 related. Enabling R8 automatically enables D8. With R8 disabled and D8 enabled we still have this issue on a test Vivo Y33 device. With D8 disabled issue goes away.

This issue needs to be escalated to top priroity as D8 is enabled by default in the latest studios.

With D8 enabled, we cannot even finish APK installation on Vivo Y33 device. This is a not a runtime issue but a compile issue with Mediatek VM and D8 since our install issue signals the Mediatek device cannot even compile the apk dex files to local runable form.

@diegomontoya it'd be really appreciated if you could file an issue to D8 via https://issuetracker.google.com/issues/new?component=317603

I wonder missing definition of and/or part of DebugMetadata annotation is causing issues on lollipop. Could someone try and confirm that adding the following rule could be a workaround?

-keep,allowobfuscation @interface kotlin.coroutines.jvm.internal.DebugMetadata

@jsjeon

With the extra proguard rule we have an improvement. At least the Vivo Y33 can now complete APK install. =)

However, execution the app still result in the following stack:

12-14 15:21:07.039 10591-10591/com.xxx E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.xxx, PID: 10591
java.lang.IncompatibleClassChangeError: Couldn't find kotlin.coroutines.jvm.internal.DebugMetadata.i
at libcore.reflect.AnnotationAccess.toAnnotationInstance(AnnotationAccess.java:659)
at libcore.reflect.AnnotationAccess.toAnnotationInstance(AnnotationAccess.java:641)
at libcore.reflect.AnnotationAccess.getDeclaredAnnotation(AnnotationAccess.java:170)
at libcore.reflect.AnnotationAccess.getAnnotation(AnnotationAccess.java:72)
at java.lang.Class.getAnnotation(Class.java:343)
at kotlin.coroutines.jvm.internal.DebugMetadataKt.getDebugMetadataAnnotation(DebugMetadata.kt:48)
at kotlin.coroutines.jvm.internal.DebugMetadataKt.getStackTraceElement(DebugMetadata.kt:40)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.getStackTraceElement(ContinuationImpl.kt:75)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.toString(ContinuationImpl.kt:67)
at kotlin.coroutines.jvm.internal.SuspendLambda.toString(ContinuationImpl.kt:165)
at java.lang.StringBuilder.append(StringBuilder.java:202)
at kotlinx.coroutines.DebugKt.toDebugString(Debug.kt:46)
at kotlinx.coroutines.DispatchedContinuation.toString(Dispatched.kt:183)
at java.lang.StringBuilder.append(StringBuilder.java:202)
at android.os.Looper.loop(Looper.java:145)
at android.app.ActivityThread.main(ActivityThread.java:5560)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:965)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:760)
12

The following modified proguard rule however fixed the problem for minSdk=21. If you are targeting mindSdk=15, the following solution does not work.

try to fix R8 issue with kotlin coroutines

-keep,allowobfuscation @interface kotlin.coroutines.jvm.internal.** { *; }

EDIT: Update answer to reflect that the solution does not work for minSdk=15

Facing the same crash since switching to R8 in prod :(

java.lang.IncompatibleClassChangeError: 
  at libcore.reflect.AnnotationAccess.toAnnotationInstance (AnnotationAccess.java:659)
  at libcore.reflect.AnnotationAccess.toAnnotationInstance (AnnotationAccess.java:641)
  at libcore.reflect.AnnotationAccess.getDeclaredAnnotation (AnnotationAccess.java:170)
  at libcore.reflect.AnnotationAccess.getAnnotation (AnnotationAccess.java:72)
  at java.lang.Class.getAnnotation (Class.java:343)
  at kotlin.coroutines.jvm.internal.BaseContinuationImpl.kotlin.coroutines.jvm.internal.DebugMetadataKt.getDebugMetadataAnnotation (ContinuationImpl.kt:2)
                                                         kotlin.coroutines.jvm.internal.DebugMetadataKt.getStackTraceElement
                                                         getStackTraceElement
                                                         toString
  at kotlin.coroutines.jvm.internal.SuspendLambda.toString (ContinuationImpl.kt:4)
  at java.lang.StringBuilder.append (StringBuilder.java:202)
  at kotlinx.coroutines.DebugKt.toDebugString (Debug.kt:2)
  at kotlinx.coroutines.DispatchedContinuation.toString (Dispatched.kt:1)
  at java.lang.StringBuilder.append (StringBuilder.java:202)
  at android.os.Looper.loop (Looper.java:160)
  at android.app.ActivityThread.main (ActivityThread.java:5576)
  at java.lang.reflect.Method.invoke (Native Method)
  at java.lang.reflect.Method.invoke (Method.java:372)
  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run (ZygoteInit.java:956)
  at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:751)

The question would be what is the purpose of that code? From naming it seems to be debug stuff that should probably not be used in production?

Ok so after some digging the call is made by the Looper when it have a setMessageLogging set.
I suppose some devices have it by default ....

During the Looper.loop:

if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

Is called to log what pass via it.
That calls DispatchedContinuation.toString() that returns "DispatchedContinuation[$dispatcher, ${continuation.toDebugString()}]" (That calls the method that will then fail because of R8)

1) Since setMessageLogging is a public API, should DispatchedContinuation.toString call the debug call that will read annotations?
2) Since it's not only used for debug I suppose that there's is the need for a proguard rule to keep the annotation in all cases if this code is to stay.

Those point are independent of the R8 issue.
Ping @qwwdfsad as I suppose this is the needed information to understand the root cause.

If R8 does not find the bug, having a fix on coroutine side would be nice.

I've tried to reproduce this issue in R8's testing env. to see if we're missing what we should do: https://r8-review.googlesource.com/c/r8/+/32082, but then found a simple, self-contained reproduction that is failing even on JVM. Android legacy VMs have different logic to retrieve annotations, and thus we're seeing different errors. Anyhow, here is the reproducing steps:
coroutines-issue-858.zip

$ unzip coroutines-issue-858.zip
Archive:  coroutines-issue-858.zip
  inflating: internal_annotation/Annotation.kt  
  inflating: internal_annotation/Base.kt  
  inflating: internal_annotation/main.kt  
$ ~/projects/r8/third_party/kotlin/kotlinc/bin/kotlinc-jvm -version
info: kotlinc-jvm 1.2.30 (JRE 1.8.0_181-8u181-b13-2~deb9u1-b13)
$ ~/projects/r8/third_party/kotlin/kotlinc/bin/kotlinc-jvm internal_annotation/
$ java -cp ~/projects/r8/third_party/kotlin/kotlinc/lib/kotlin-stdlib.jar:. internal_annotation.MainKt
Impl::toString
Exception in thread "main" java.lang.NoSuchMethodError: internal_annotation.Annotation.f2()Ljava/lang/String;
    at internal_annotation.AnnotationKt.foo(Annotation.kt:17)
    at internal_annotation.MainKt.main(main.kt:9)

Not sure it's because of using that old kotlinc. Looking at generated bytecode:

$ javap -c -p internal_annotation/Annotation.class
Compiled from "Annotation.kt"
public interface internal_annotation.Annotation extends java.lang.annotation.Annotation {
  public abstract int field1();

  public abstract java.lang.String field2();
}
$ javap -c -p internal_annotation/AnnotationKt.class
Compiled from "Annotation.kt"
public final class internal_annotation.AnnotationKt {
  public static final java.lang.StackTraceElement foo(internal_annotation.Base);
    Code:
       0: aload_0
       1: ldc           #12                 // String $receiver
       3: invokestatic  #18                 // Method kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V
       6: aload_0
       7: invokestatic  #22                 // Method getMyAnnotation:(Linternal_annotation/Base;)Linternal_annotation/Annotation;
      10: dup
      11: ifnull        17
      14: goto          20
      17: pop
      18: aconst_null
      19: areturn
      20: astore_1
      21: new           #24                 // class java/lang/StackTraceElement
      24: dup
      25: aload_1
      26: invokeinterface #30,  1           // InterfaceMethod internal_annotation/Annotation.f2:()Ljava/lang/String;
      31: aload_1
      32: invokeinterface #30,  1           // InterfaceMethod internal_annotation/Annotation.f2:()Ljava/lang/String;
      37: aload_1
      38: invokeinterface #30,  1           // InterfaceMethod internal_annotation/Annotation.f2:()Ljava/lang/String;
      43: aload_1
      44: invokeinterface #34,  1           // InterfaceMethod internal_annotation/Annotation.f1:()I
      49: invokespecial #38                 // Method java/lang/StackTraceElement."<init>":(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)V
      52: areturn

  private static final internal_annotation.Annotation getMyAnnotation(internal_annotation.Base);
    Code:
       0: aload_0
       1: invokevirtual #45                 // Method java/lang/Object.getClass:()Ljava/lang/Class;
       4: ldc           #26                 // class internal_annotation/Annotation
       6: invokevirtual #51                 // Method java/lang/Class.getAnnotation:(Ljava/lang/Class;)Ljava/lang/annotation/Annotation;
       9: checkcast     #26                 // class internal_annotation/Annotation
      12: areturn
}

Indeed, there is no f2() in Annotation.class, while there are attempts to call it:

      26: invokeinterface #30,  1           // InterfaceMethod internal_annotation/Annotation.f2:()Ljava/lang/String;

So, the issue is, in the annotation definition:

$ cat internal_annotation/Annotation.kt
...
@Target(AnnotationTarget.CLASS)
internal annotation class Annotation(
    @get:JvmName("f1")
    val field1: Int = 0,
    @get:JvmName("f2")
    val field2: String = ""
)
...

there are implicit name mappings from field2 to f2 and the field access is replaced with the getter:

$ cat internal_annotation/Annotation.kt
...
@JvmName("foo")
internal fun Base.foo(): StackTraceElement? {
  val anno = getMyAnnotation() ?: return null
  return StackTraceElement(anno.field2, anno.field2, anno.field2, anno.field1) // <-- here f2() is not found.
}
...

Proguard/R8 can remedy this by using -applymapping. In my testing setting,

$ cat getter-mapping.txt
...
internal_annotation.Annotation -> internal_annotation.Annotation:
  int f1() -> field1
  java.lang.String f2() -> field2

and adding:

-applymapping getter-mapping.txt

to the proguard config replaced interface call to f2() with field2() and got the expected output:

Impl::toString
Impl::Annotation::field2.Impl::Annotation::field2(Impl::Annotation::field2:2)

So, all exception reports above that end with

Fatal Exception: java.lang.IncompatibleClassChangeError: Couldn't find kotlin.c.b.a.f.i

or with different package hierarhcy are due to missing getter that is defined at this line:
https://github.com/JetBrains/kotlin/blob/master/libraries/stdlib/jvm/src/kotlin/coroutines/jvm/internal/DebugMetadata.kt#L23

@diegomontoya Seems it worked on my app after half day update, no issues for now!

Oops, using the latest kotlic generates the correct bytecode:

$ ~/Downloads/kotlinc/bin/kotlinc-jvm -version
info: kotlinc-jvm 1.3.10 (JRE 1.8.0_181-8u181-b13-2~deb9u1-b13)
$ ~/Downloads/kotlinc/bin/kotlinc-jvm internal_annotation/
$ java -cp ~/projects/r8/third_party/kotlin/kotlinc/lib/kotlin-stdlib.jar:. internal_annotation.MainKt
Impl::toString
Impl::Annotation::field2.Impl::Annotation::field2(Impl::Annotation::field2:2)
$ javap -c -p internal_annotation/Annotation.class
Compiled from "Annotation.kt"
public interface internal_annotation.Annotation extends java.lang.annotation.Annotation {
  public abstract int f1();

  public abstract java.lang.String f2();
}

I'll update R8's test infra and see what we're missing. Will use https://issuetracker.google.com/issues/120951621 to update investigation.

For now I workarounded with https://github.com/Tolriq/kotlinx.coroutines/commit/aa0db29d80b46ab08f4419a517a7bac0af896e11 as I do not think debug code should be called in standard toString() called from normal usage of Looper.

A proper fix with updated test is probably needed in all cases,

We'll revert this change while Android team investigates the issue.

as I do not think debug code should be called in standard toString() called from normal usage of Looper.

It is not a debug code, it is "debug metadata" and there is nothing wrong with its usage.

The toString can be called by normal OS code is place as important as Looper.loop().

Reading annotations at that place does not sound normal as reading annotation is still not cheap.

Since the issue is OS and device specific, it's not even sure R8 will be able to workaround those, I don't know why those device have looper logging enabled by default, but since it is not something we have control over, having that log not impacting performance is something to look after IMO.

@kimcy929 @Tolriq I would highly recommend avoiding R8 + kotlin coroutines at this point for production. After testing master branch R8 (not the one bundled with 3.3RC2), we found regressions as compared to 3.3RC2 R8 where entire lambdas/blocks of code is stripped while using select/onTimeOut/coroutines combo. We are still trying to boild down the test code to demonstate the new bug in the latest unreleased versin of R8. To be on the safe side, stick with proguard for now.

@diegomontoya I've been working with R8 team since August to have R8 working for my app the remaining issues are small and will be fixed.

If there's regressions in R8 1.4 then I'll start again the same process, just open issues there even without small repro, the team is reactive and motivated to have something good.

Proguard 6.0.3 is bugged for my use case preventing me to update some other libraries that fix some bugs that affect me. So helping R8 team is the way to go and having large beta group + millions prod users to have all the necessary special cases / devices so they can have a perfect product is better than waiting for fixes for bugs that they won't be aware of ;)

Going coroutines that early impact my 99,999% crash free sessions due to https://github.com/Kotlin/kotlinx.coroutines/issues/490 and this one, but globally it's needed to have things moving. This particular issue or https://github.com/Kotlin/kotlinx.coroutines/issues/878 have workarounds so we can just use them until proper solutions are made.

@diegomontoya Thank you! But many user updated my app with R8 + Kotlin coroutines :D

Reading annotations at that place does not sound normal as reading annotation is still not cheap.

Yes, but logging everything in the main loop does not sound normal as well.
While there is no evidence that this is the real issue, I prefer diagnostic over potential performance.

@qwwdfsad This is Android source code: https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/os/Looper.java#168 So we can assume the toString can be called from here on some devices with logging enabled.

The crash I faced:

java.lang.IncompatibleClassChangeError: 
  at libcore.reflect.AnnotationAccess.toAnnotationInstance (AnnotationAccess.java:659)
  at libcore.reflect.AnnotationAccess.toAnnotationInstance (AnnotationAccess.java:641)
  at libcore.reflect.AnnotationAccess.getDeclaredAnnotation (AnnotationAccess.java:170)
  at libcore.reflect.AnnotationAccess.getAnnotation (AnnotationAccess.java:72)
  at java.lang.Class.getAnnotation (Class.java:343)
  at kotlin.coroutines.jvm.internal.BaseContinuationImpl.kotlin.coroutines.jvm.internal.DebugMetadataKt.getDebugMetadataAnnotation (ContinuationImpl.kt:2)
                                                         kotlin.coroutines.jvm.internal.DebugMetadataKt.getStackTraceElement
                                                         getStackTraceElement
                                                         toString
  at kotlin.coroutines.jvm.internal.SuspendLambda.toString (ContinuationImpl.kt:4)
  at java.lang.StringBuilder.append (StringBuilder.java:202)
  at kotlinx.coroutines.DebugKt.toDebugString (Debug.kt:2)
  at kotlinx.coroutines.DispatchedContinuation.toString (Dispatched.kt:1)
  at java.lang.StringBuilder.append (StringBuilder.java:202)
  at android.os.Looper.loop (Looper.java:160)
  at android.app.ActivityThread.main (ActivityThread.java:5576)
  at java.lang.reflect.Method.invoke (Native Method)
  at java.lang.reflect.Method.invoke (Method.java:372)
  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run (ZygoteInit.java:956)
  at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:751)

Is pretty clear about where it happens and the issue is probably a VM issue on those devices, the source of the issue is clear too, toString call that function that gather annotations.

I removed that call in my custom coroutine build and no more crashes in prod.

Most other calls have nameString or other ways to gather diags when needed. And all your work about debugging can still use that. It's just that precise call on a place that can be called in non debug way by the system (At least Android) that is IMO problematic from a performance point of view.

@qwwdfsad

We'll revert this change while Android team investigates the issue.

Could you let us know what change you're going to revert?

@jsjeon reading debug metadata in DispatchedContinuation.toString

The following modified proguard rule however fixed the problem for minSdk=21. If you are targeting mindSdk=15, the following solution does not work.

try to fix R8 issue with kotlin coroutines

-keep,allowobfuscation @interface kotlin.coroutines.jvm.internal.** { *; }

EDIT: Update answer to reflect that the solution does not work for minSdk=15

Could you try

-keep @interface kotlin.coroutines.jvm.internal.DebugMetadata { *; }

instead? I.e., without allowobfuscation?

@qwwdfsad Thank you for adding a workaround on coroutine side. We found an issue on R8, and hopefully we can fix it before we're branching for AS 3.4

-keep @interface kotlin.coroutines.jvm.internal.DebugMetadata { *; }

It worked for me

Was this page helpful?
0 / 5 - 0 ratings