R8 stripping out Kotlin companion object needed for reflection - android

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?

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.

Related

Unable to create converter for class when enabling minify in gradle

I am trying to enable minifyEnabled true in my gradle file for deployment but i am getting the below error:
java.lang.IllegalArgumentException: Unable to create converter for class com.th3pl4gu3.mes.models.MesResponse
for method MesApiService.getServices
My code is as follows
AppContainer.kt
/**
* Dependency Injection container at the application level.
*/
interface AppContainer {
val onlineServiceRepository: ServiceRepository
}
/**
* Implementation for the Dependency Injection container at the application level.
*
* Variables are initialized lazily and the same instance is shared across the whole app.
*/
class DefaultAppContainer(private val context: Context): AppContainer {
private val BASE_URL = "https://..."
/**
* Use the Retrofit builder to build a retrofit object using a kotlinx.serialization converter
*/
#OptIn(ExperimentalSerializationApi::class)
private val retrofit: Retrofit = Retrofit.Builder()
.addConverterFactory(Json.asConverterFactory("application/json".toMediaType()))
.baseUrl(BASE_URL)
.build()
/**
* Retrofit service object for creating api calls
*/
private val retrofitService: MesApiService by lazy {
retrofit.create(MesApiService::class.java)
}
}
ServiceRepository.kt
/**
* Repository that fetch service list from Mes API.
*/
interface ServiceRepository {
/** Fetches list of services from Mes API */
suspend fun getMesServices(): MesResponse
}
MesApiServiceRepository.kt
class MesApiServiceRepository(
private val mesApiService: MesApiService
) : ServiceRepository {
/** Fetches list of Services from Mes API */
override suspend fun getMesServices(): MesResponse = mesApiService.getServices()
}
MesResponse.kt
#kotlinx.serialization.Serializable
data class MesResponse(
val services: List<Service>,
val message: String,
val success: Boolean
)
proguard-rules.pro
#####################################
########## Kotlin Metadata ##########
#####################################
-dontwarn org.jetbrains.annotations.**
-keep class kotlin.Metadata { *; }
##################################
########### MES Models ###########
##################################
-keep class com.th3pl4gu3.mes.models.MesResponse
-keep class com.th3pl4gu3.mes.models.** {*;}
-keep class com.th3pl4gu3.mes.data.** {*;}
-keep class com.th3pl4gu3.mes.api.** {*;}
-keep class com.th3pl4gu3.mes.di.** {*;}
###############################
########## Retrofit2 ##########
###############################
# Retrofit does reflection on generic parameters. InnerClasses is required to use Signature and
# EnclosingMethod is required to use InnerClasses.
-keepattributes Signature, InnerClasses, EnclosingMethod
# Retrofit does reflection on method and parameter annotations.
-keepattributes RuntimeVisibleAnnotations, RuntimeVisibleParameterAnnotations
# Keep annotation default values (e.g., retrofit2.http.Field.encoded).
-keepattributes AnnotationDefault
# Retain service method parameters when optimizing.
-keepclassmembers,allowshrinking,allowobfuscation interface * {
#retrofit2.http.* <methods>;
}
# Ignore annotation used for build tooling.
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
# Ignore JSR 305 annotations for embedding nullability information.
-dontwarn javax.annotation.**
# Guarded by a NoClassDefFoundError try/catch and only used when on the classpath.
-dontwarn kotlin.Unit
# Top-level functions that can only be used by Kotlin.
-dontwarn retrofit2.KotlinExtensions
-dontwarn retrofit2.KotlinExtensions$*
# With R8 full mode, it sees no subtypes of Retrofit interfaces since they are created with a Proxy
# and replaces all potential values with null. Explicitly keeping the interfaces prevents this.
-if interface * { #retrofit2.http.* <methods>; }
-keep,allowobfuscation interface <1>
# Keep inherited services.
-if interface * { #retrofit2.http.* <methods>; }
-keep,allowobfuscation interface * extends <1>
# Keep generic signature of Call, Response (R8 full mode strips signatures from non-kept items).
-keep,allowobfuscation,allowshrinking interface retrofit2.Call
-keep,allowobfuscation,allowshrinking class retrofit2.Response
# With R8 full mode generic signatures are stripped for classes that are not
# kept. Suspend functions are wrapped in continuations where the type argument
# is used.
-keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation
Can someone please help me out ?
I was able to fix it by adding #Keep to the MesResponse class.
#Keep
#kotlinx.serialization.Serializable
data class MesResponse(
val services: List<Service>,
val message: String,
val success: Boolean
)

How to properly add proguard to application?

I have added pro guards to my Android application, but it makes too much on resposne files and provide to crash an application during launching.
This is my build.gradle settings of optimizing and reducing code
minifyEnabled true
shrinkResources true
debuggable false
manifestPlaceholders = [crashlyticsCollectionEnabled:"true"]
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
in android manifest
<application
<meta-data
android:name="firebase_crashlytics_collection_enabled"
android:value="${crashlyticsCollectionEnabled}" />
[...]
in proguard-rules.pro
-keep class com.google.android.gms.** { *; }
-keep class com.google.firebase.** { *; }
-keep class com.package.packageandroid2.endpoints.responses.** { *; }
-keep class * extends com.package.packageandroid2.endpoints.** { *; }
-keep class * extends com.package.packageandroid2.PackageApplication.** { *; }
-keep class * extends com.package.packageandroid2.** { *;}
error
Process: com.package.dev.staging, PID: 9574
java.lang.RuntimeException: Unable to create application com.package.packageandroid2.PackageApplication: zh.d: Could not create instance for [Single:'a9.p']
at android.app.ActivityThread.handleBindApplication(ActivityThread.java:6465)
This is the place where is Single class
package com.package.packageandroid2.endpoints.responses
sealed class ResponseId {
abstract val count: Int
data class Single(val id: Int) : ResponseId() {
override val count: Int = 1
}
data class Multiple(val ids: List<Int>) : ResponseId() {
override val count: Int = ids.count()
}
}
From my perspective I keep classes that could provide to crash. What could be the problem here?

Android Kotlin ProGuard Rules Wrong?

Currently I have a library that is to be consumed by multiple other projects, however I have a problem with the code obfuscation when consuming the .aar in particular with some classes:
This one to handle backend responses or asynchronous tasks, etc.
sealed class Result<out T : Any> {
open class Success<out T : Any>(val data: T) : Result<T>()
open class Error(val error: ErrorModel) : Result<Nothing>()
}
And this one to act as a initializer or something like it to the library:
class LibApp private constructor(
val appContext: Context
) {
companion object {
#JvmStatic lateinit var instance: LibApp
private set
fun init(
appContext: Context
) {
if (this::instance.isInitialized.not()) {
instance = LibApp(appContext)
}
}
}
}
The rules I'm using are:
#noinspection ShrinkerUnresolvedReference
-keep class com.cross.project.compilation.testlib.response.** { *; }
-keep class com.cross.project.compilation.testlib.LibApp.** { *; }
Currently I have 3 build types and 2 flavors:
buildTypes {
release {
debuggable false
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
releaseDebug{
debuggable true
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
debug{
debuggable true
minifyEnabled false
}
}
flavorDimensions "libFlavors"
productFlavors {
dev {
}
prod {
}
}
When I check the decompiled sources for release variants in the consumer project I get:
Result gets the < T > stripped from the Success return:
public sealed class Result<out T : kotlin.Any> private constructor() {
public open class Error public constructor(error: com.cross.project.compilation.testlib.response.ErrorModel) : com.cross.project.compilation.testlib.response.Result {
public final val error: com.cross.project.compilation.testlib.response.ErrorModel /* compiled code */
}
public open class Success<out T : kotlin.Any> public constructor(data: T) : com.cross.project.compilation.testlib.response.Result {
public final val data: T /* compiled code */
}
}
This changes the way something is coded within the variants, from something like this in dev:
return when (val response = accountService.getAccountData()) {
is Result.Success -> ConsumerResult.Success(response.data)
is Result.ErrorModel -> ConsumerResult.Failure(response.errorModel)
}
To this:
return when (val response = accountService.getAccountData()) {
is Result.Success<*> -> ConsumerResult.Success((response.data as AccountData))
is Result.ErrorModel -> ConsumerResult.Failure(response.errorModel)
}
The LibApp class gets removed for some reason.
I've tried to modify the rules to avoid these issues, but only succeding in keeping the LibApp class by applying the following:
-keepclasseswithmembers class com.cross.project.compilation.testlib.LibApp {
public *;
}
-keep #interface kotlin.Metadata {
*;
}
-keepattributes RuntimeVisibleAnnotations
And also add the #Keep annotation to every level in the class like:
#Keep class LibApp private constructor(
...
#Keep
companion object {
...
#Keep
fun init(
...
However I've had no luck modifying the rule to keep the generic return of the Result.Success, Any idea of what I'm doing wrong?
As an additional information, I'm using and building with the maven-publish plugin:
Android Gradle Plugin 4.0.1
Gradle Wrapper 6.1.1
This problem seems to have appeared after I upgraded from:
Android Gradle Plugin 3.6.3
Gradle Wrapper 5.6.4
The type Nothing in the Result.Error is erased while obfuscating by R8. Try to add in build.gradle file the following:
buildscript {
repositories {
// other repos are omitted
maven { url 'https://storage.googleapis.com/r8-releases/raw' }
}
dependencies {
classpath 'com.android.tools:r8:2.1.68'
// other dependencies are omitted
In proguard file declare the Result type to keep from obfuscation
-keep class [path_to_class].Result { *; }
-keep class [path_to_class].Result$** { *; }

Android Koin NoBeanDefFoundException only in release version of app

In Debug version it is working fine, but fails on Release version. I guess it is related to Proguard rules.
// Koin
implementation 'org.koin:koin-android:1.0.2'
implementation 'org.koin:koin-android-viewmodel:1.0.2'
Modules:
val appModule = module {
viewModel<IntroVM>()
}
val dataModule = module {
single { NetworkManager(androidContext().applicationContext) }
single { LocalFileManager(androidContext().applicationContext) }
single { ApiModel(get()) }
Class definitions:
class ApiModel(val networkManager: NetworkManager){}
class LocalFileManager(private var appContext: Context) {}
class NetworkManager(private var appContext: Context) {}
class IntroVM(var apiModel: ApiModel) : CommonVM() {
Koin init:
startKoin(this, listOf(appModule, dataModule))
Proguard rules:
-keepattributes *Annotation*
-keepattributes SourceFile,LineNumberTable
-keep public class * extends java.lang.Exception
-keepclassmembernames class kotlinx.** {
volatile <fields>;
}
StackTrace:
Error while resolving instance for class 'com...viewModels.auth.b' -
error: org.koin.error.BeanInstanceCreationException:
Can't create definition for 'Factory [name='b',class='com..viewModels.auth.b', binds~(androidx.lifecycle.r)]' due to error :'binds~(androidx.lifecycle.r)]
No constructor found for class 'class com...viewModels.auth.b'
As you are using androidX
Use below library of viewmodel for koin
implementation 'org.koin:koin-androidx-viewmodel:1.0.2'
This issue provided needed solution

ProGuard rules for Kotlin reflection

Here's a very simple class:
class MainActivity : AppCompatActivity() {
val prop: String = "test"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d("MainActivity", ::prop.name)
}
}
If I run this application with ProGuard using the following rules:
-dontwarn kotlin.**
-dontwarn org.w3c.dom.events.*
-dontwarn org.jetbrains.kotlin.di.InjectorForRuntimeDescriptorLoader
I get this exception:
a.d.g: Property 'prop' not resolved in class com.cypressworks.kotlinreflectionproguard.MainActivity
Which ProGuard rules do I need to apply to make it work?
If ProGuard is removing your attribute, the dontwarn rule will only hide the warning messages. What you need is to actually tell ProGuard to keep it.
One possible way could be:
-keepclassmembers public class com.cypressworks.kotlinreflectionproguard.** {
public * *;
}

Categories

Resources