Android Kotlin ProGuard Rules Wrong? - android

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$** { *; }

Related

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?

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?
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.

Getting `java.lang.AssertionError: java.lang.NoSuchFieldException: HTTP_1_0`

In my project, I am performing API call using RxJava.
Without proguard, it is running fine. But when I apply proguard It gives java.lang.AssertionError: java.lang.NoSuchFieldException: HTTP_1_0
in onError(e: Throwable) of a subscriber.
I applied -keepclassmembers enum * { *; } in my proguard to prevent obfuscation.
Api Call
fun latestPosts(): Subscription {
return service.latestPosts
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(APICallSubscriber(presenterContract, ApiIndex.POSTS))
}
Subscriber
class APICallSubscriber<T>(private val callback: BasePresenterContract,
private val apiIndex: String) : Subscriber<Response<T>>() {
override fun onCompleted() {
}
override fun onError(e: Throwable) {
Log.d("HomeTest", "${e}")
}
override fun onNext(response: Response<T>) {
val jsonObject = App.gson().toJsonTree(response).asJsonObject
val responseCode = jsonObject
.get(PayloadKeys.RAW_RESPONSE).asJsonObject
.get(PayloadKeys.CODE).asInt
Log.d("HomeTest", "$jsonObject")
val body: JsonElement? = jsonObject.get(PayloadKeys.BODY)
if (body != null) {
val responseBody = body.asJsonObject
callback.onNetworkRequestCompletedWith(responseBody, responseCode, apiIndex)
} else {
val errorBody: JsonElement? = jsonObject.get(PayloadKeys.ERROR_BODY)
callback.onNetwordRequestError(errorBody!!.asJsonObject, apiIndex)
}
}
}
I have tried different proguard rules but no result.
Please help.
In case this helps anyone, I had an existing project with a library module I had made. To prevent my app from crashing after being minified by ProGuard, I needed to add
-keepclassmembers enum * { *; }
to not only the library's build.gradle, but also to the main module's build.gradle.
I was facing the same issue, I solved by adding all these in proguard-rules.pro file:
-keepclassmembers enum * { *; }
-keep class com.google.code.gson.* { *; }
-keepattributes *Annotation*, Signature, Exception
-keepclassmembers,allowobfuscation class * {
#com.google.gson.annotations.SerializedName <fields>;
}
below configuration always worked for me
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}

Kotlin Reflection: primaryConstructor not found with R8 obfuscation

In my current Kotlin Android project I just had to face the following problem:
For this class hierarchy
abstract class MyClass {
...
}
class MySubClassA: MyClass() {
...
}
class MySubClassB: MyClass() {
...
}
class MySubClassC: MyClass() {
...
}
I wrote a creator function like this:
private fun <T : MyClass> createMyClass(myClassType: KClass<T>): T? {
val constructor = myClassType.primaryConstructor
return constructor?.call()?.apply {
...
}
}
This worked nicely for the debug version but for the release version with R8 obfuscation turned on the constructor was alway null. My settings for R8 were this:
-keep class kotlin.** { *; }
-keep class kotlin.Metadata { *; }
-dontwarn kotlin.**
-keepclassmembers class **$WhenMappings {
<fields>;
}
-keepclassmembers class kotlin.Metadata {
public <methods>;
}
-assumenosideeffects class kotlin.jvm.internal.Intrinsics {
static void checkParameterIsNotNull(java.lang.Object, java.lang.String);
}
-keep class kotlin.reflect.jvm.internal.impl.builtins.BuiltInsLoaderImpl
After some reverse engineering of the Kotlin reflection API I came up with the following workaround:
private fun <T : MyClass> createMyClass(myClassType: KClass<T>): T? {
val constructor = myClassType.constructors.firstOrNull() {
it.valueParameters.isEmpty()
}
return constructor?.call()?.apply {
...
}
}
This seems to indicate, that the constructor information of the class is preserved by R8 but the meta information about a constructor being primary gets lost.
Does anybody know how to solve this in general?
I came across this issue and was able to resolve it using the #Keep annotation.
abstract class MyClass {
}
class MySubClassA #Keep constructor(): MyClass() {
}
class MySubClassB #Keep constructor(): MyClass() {
}
class MySubClassC #Keep constructor(): MyClass() {
}

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

Categories

Resources