Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

R8 stripping out Kotlin companion object needed for reflection

I have a class with a companion object which implements a factory interface.

class GoalInspectorData(
    ...
) {

    companion object : DataClassFactory<GoalInspectorData> {

        override fun fromV8Object(v8Object: V8Object): GoalInspectorData {
            ...
        }
    }
}

I have some code which examines this class at runtime using reflection to see if the class provides a factory method. It does this by checking to see if the class has a companion object (companionObjectInstance) and, if so, if that companion object implements the factory interface.

internal inline fun <reified T> convert(obj: Any): T {

    val companionObject = T::class.companionObjectInstance

    @Suppress("UNCHECKED_CAST")
    return when {
        T::class in builtInClasses -> obj as T
        companionObject as? DataClassFactory<T> != null -> companionObject.fromV8Object(obj as V8Object)
        else -> throw IllegalArgumentException("No converter for type ${T::class}")
    }
}

This all works fine in a debug build.

It fails in a release build with R8 enabled (minifyEnabled true in build.gradle). It fails because companionObjectInstance returns null.

I'm using the don't optimize Proguard config:

proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'

and in my own proguard-rules.pro I've added just about every -keep rule I can imagine in an attempt to retain this companion object, and added @Keep annotations to everything, but nothing works. R8 is determined to strip it out.

For example:

-keep class my.package.** {
    *;
}
-keep interface my.package.** {
    *;
}

-if class **$Companion extends **
-keep class <2>
-if class **$Companion implements **
-keep class <2>

Are there any other -keep rules or configuration options which would instruct R8 to retain this companion object?

like image 857
Graham Borland Avatar asked Sep 17 '19 12:09

Graham Borland


People also ask

Why does Kotlin need a companion object?

companion object is how you define static variables/methods in Kotlin. You are not supposed to create a new instance of Retrofit / ApiService each time you execute a request, however.

How do you get the Kotlin companion object?

To create a companion object, you need to add the companion keyword in front of the object declaration. The output of the above code is “ You are calling me :) ” This is all about the companion object in Kotlin. Hope you liked the blog and will use the concept of companion in your Android application.

What is the difference between companion object and object in Kotlin?

An object, or an object declaration, is initialized lazily, when accessed for the first time. A companion object is initialized when the corresponding class is loaded. It brings about the 'static' essence, although Kotlin does not inherently support static members. Show activity on this post.


1 Answers

First of all, the keep rule

-keep class my.package.** {
    *;
}

should be sufficient to keep all the classes - including companion classes in your program. You should not need the -dontoptimize flag, so using the configuration proguard-android-optimize.txt should be fine.

However, as you use Kotlin reflection you probably also need to keep the annotation class kotlin.Metadata and runtime visible annotations with these rules:

-keep @interface kotlin.Metadata {
  *;
}
-keepattributes RuntimeVisibleAnnotations

If that does still not work could you please file an R8 issue? If you can include a simple reproduction that would be great.

like image 161
sgjesse Avatar answered Oct 02 '22 18:10

sgjesse