Skip to content

Insufficient keep rules for R8 in full mode #3005

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
christofferqa opened this issue Jan 18, 2019 · 16 comments
Closed

Insufficient keep rules for R8 in full mode #3005

christofferqa opened this issue Jan 18, 2019 · 16 comments
Labels

Comments

@christofferqa
Copy link
Contributor

The keep rules in https://github.com/square/retrofit/blob/master/retrofit/src/main/resources/META-INF/proguard/retrofit2.pro are insufficient for R8 in full mode.

See for example https://issuetracker.google.com/issues/120168590#comment3 and https://issuetracker.google.com/issues/122940365#comment9.

As a simple example, consider the 'SimpleService' sample:
https://github.com/square/retrofit/blob/master/samples/src/main/java/com/example/retrofit/SimpleService.java

The interface 'GitHub' is never instantiated in the code (except via reflection), and it is not explicitly kept by a keep rule, so R8 can safely replace all values in the program that have type 'GitHub' with constant null.

@JakeWharton
Copy link
Collaborator

Can the use of a Proxy to create instances be detected? The Class literal flows directly into a method which calls Proxy.newProxyInstance in the common case.

@christofferqa
Copy link
Contributor Author

R8 does not have any interprocedural tracking of class literals at this point. Even with such tracking, the keep rules would still be insufficient depending on the program.

It should be possible to keep the relevant interfaces if they are used by the program with a rule inspired by the following one.

-if interface * { @retrofit2.http.* <methods>; }
-keep,allowobfuscation interface <1>

@JakeWharton
Copy link
Collaborator

I guess that's not that terrible because the methods can still be removed. You might wind up with an empty interface or two if you have unused ones coming from libraries.

@JakeWharton
Copy link
Collaborator

I've got another full mode problem. R8 replaces interfaces used as return types in service interface methods with a subtype which then breaks reflective lookup.

Is there a way to issue a -keep on all types used in return types of an interface with a Retrofit annotation?

@JakeWharton JakeWharton reopened this Feb 11, 2019
@christofferqa
Copy link
Contributor Author

You should be able to use the following rule:

-if interface * { @retrofit2.http.* *** *(...); }
-keep,allowobfuscation interface <3>

This is assuming that the return type is an interface. If the return type could also be a class or enum type, then the following rules are unfortunately also needed:

-if interface * { @retrofit2.http.* *** *(...); }
-keep,allowobfuscation class <3>

-if interface * { @retrofit2.http.* *** *(...); }
-keep,allowobfuscation enum <3>

It would be easy to extend the grammar a bit, though. I am considering if this rule should not be written as:

-if interface * { @retrofit2.http.* *** *(...); }
-keep,allowobfuscation basetype(<3>)

This rule should then suffice to handle the cases where the return type is an array, class, enum, and interface. This would no longer be compatible with Proguard, though.

@JakeWharton
Copy link
Collaborator

The duplication is okay since Retrofit is the only one that needs to ship those rules. But I can't get those rules to actually work. I tried with just -keep as well. R8 still rewrites the return types.

Here's my input:

@GET("_s/getsuggestions?p=/&s=irina&c=3")
fun list(): Deferred<Map<String, List<Item>>>

However, upon more investigation as to what's happening, R8 is performing vertical class merging of the kotlinx.coroutines.Deferred interface into kotlinx.coroutines.CompletableDeferred but it's also (correctly) updating the Retrofit Converter to look for CompletableDeferred so this should be working. The problem is that the interface isn't having its @dalvik.annotation.Signature annotation values updated so it still refers to kotlinx.coroutines.Deferred.

.method public abstract list()Lkotlinx/coroutines/CompletableDeferred;
    .annotation system Ldalvik/annotation/Signature;
        value = {
            "()",
            "Lkotlinx/coroutines/Deferred<",
            "Ljava/util/Map<",
            "Ljava/lang/String;",
            "Ljava/util/List<",
            "Lcom/jakewharton/sdksearch/api/dac/Item;",
            ">;>;>;"
        }
    .end annotation

    .annotation runtime Lretrofit2/http/GET;
        value = "_s/getsuggestions?p=/&s=irina&c=3"
    .end annotation
.end method

When using reflection on the parameterized return type, this annotation is checked for the type which is then loaded.

Caused by: java.lang.ClassNotFoundException: Didn't find class "kotlinx.coroutines.Deferred" on path: DexPathList[[zip file "/data/app/com.jakewharton.sdksearch-YFsxa6XLTfSDhK4wx_eH3g==/base.apk"],nativeLibraryDirectories=[/data/app/com.jakewharton.sdksearch-YFsxa6XLTfSDhK4wx_eH3g==/lib/x86, /system/lib]]
        at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:134)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:379)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:312)
        at java.lang.Class.classForName(Class.java:-2)
        at java.lang.Class.forName(Class.java:453)
        at libcore.reflect.ParameterizedTypeImpl.getRawType(ParameterizedTypeImpl.java:65)
        at libcore.reflect.ParameterizedTypeImpl.getRawType(ParameterizedTypeImpl.java:24)
        at retrofit2.Utils.getRawType(Utils.java:5)
        at retrofit2.CallAdapter$Factory.getRawType(CallAdapter.java:1)
        at com.jakewharton.retrofit2.adapter.kotlin.coroutines.CoroutineCallAdapterFactory.get(CoroutineCallAdapterFactory.kt:2)

This seems like an R8 bug, right? It should be updating the signature annotation when it changes the types.

@christofferqa
Copy link
Contributor Author

I can't get those rules to actually work

Have you tried with a recent version of R8, e.g., 'com.android.tools:r8:1.4.39'? I verified that a variant of the rule worked on a small test. If it doesn't work with 1.4.39, I will try to create a small repro that uses Retrofit.

(You may also try to add the rule -keep,allowobfuscation @interface retrofit2.http.*.)

This seems like an R8 bug, right?

It is. Would you mind filing an issue on the R8 bug tracker?

@JakeWharton
Copy link
Collaborator

I'm stuck on AGP 3.3.x for unrelated reasons which I'm guessing uses R8 1.3.x. I'll try with the latest R8.

@JakeWharton
Copy link
Collaborator

I couldn't get far enough to build a repro because there appears to be a regression in -if rule handling which I filed at https://issuetracker.google.com/issues/124267873 and CC'd you on.

@JakeWharton
Copy link
Collaborator

...and unblocked again. Back working on it.

@JakeWharton
Copy link
Collaborator

I can't get it to reproduce in a small project. I have filed https://issuetracker.google.com/issues/124357885 with a link to a branch of this project using AGP 3.5.0-alpha03 which reproduces the issue reliably though.

@JakeWharton
Copy link
Collaborator

Fixed upstream 🎉

vinaysshenoy pushed a commit to simpledotorg/simple-android that referenced this issue May 15, 2019
Reason:
When we moved to Android Gradle Plugin(AGP) 3.4.0, it switched from
using Proguard for minification to R8. There were a couple of
compatibility issues with Moshi, Retrofit, and R8 (links to GitHub
issues below) which caused:

1. Values of enum fields to be removed from JsonAdapters
2. Breaking of JSON parsing where an interface is implemented by
multiple response types (See DataPullResponse and its class hierarchy)

to all enums, but #2 is an invasive change since that will require us
to change how our sync works.

In addition, the Sentry gradle plugin that we were using was
incompatible with AGP 3.4.0 (or rather, Gradle 5.+ which AGP 3.4.0
requires).

Changes:
- Disable R8 for now and raise an issue on the Moshi repository regd
how to make it work with R8.
- Update the Sentry plugin to the latest version

See square/retrofit#3005
vinaysshenoy pushed a commit to simpledotorg/simple-android that referenced this issue May 15, 2019
Reason:
When we moved to Android Gradle Plugin(AGP) 3.4.0, it switched from
using Proguard for minification to R8. There were a couple of
compatibility issues with Moshi, Retrofit, and R8 (links to GitHub
issues below) which caused:

1. Values of enum fields to be removed from JsonAdapters
2. Breaking of JSON parsing where an interface is implemented by
multiple response types (See DataPullResponse and its class hierarchy)

to all enums, but #2 is an invasive change since that will require us
to change how our sync works.

In addition, the Sentry gradle plugin that we were using was
incompatible with AGP 3.4.0 (or rather, Gradle 5.+ which AGP 3.4.0
requires).

Changes:
- Disable R8 for now and raise an issue on the Moshi repository regd
how to make it work with R8.
- Update the Sentry plugin to the latest version

See square/retrofit#3005
vinaysshenoy pushed a commit to simpledotorg/simple-android that referenced this issue May 15, 2019
Reason:
When we moved to Android Gradle Plugin(AGP) 3.4.0, it switched from
using Proguard for minification to R8. There were a couple of
compatibility issues with Moshi, Retrofit, and R8 (links to GitHub
issues below) which caused:

1. Values of enum fields to be removed from JsonAdapters
2. Breaking of JSON parsing where an interface is implemented by
multiple response types (See DataPullResponse and its class hierarchy)

Problem #1 is easy to fix by adding "@JsonClass(generateAdapter=true)"
to all enums, but #2 is an invasive change since that will require us
to change how our sync works.

In addition, the Sentry gradle plugin that we were using was
incompatible with AGP 3.4.0 (or rather, Gradle 5.+ which AGP 3.4.0
requires).

Changes:
- Disable R8 for now and raise an issue on the Moshi repository regd
how to make it work with R8.
- Update the Sentry plugin to the latest version

See square/retrofit#3005
vinaysshenoy pushed a commit to simpledotorg/simple-android that referenced this issue May 15, 2019
Reason:
When we moved to Android Gradle Plugin(AGP) 3.4.0, it switched from
using Proguard for minification to R8. There were a couple of
compatibility issues with Moshi, Retrofit, and R8 (links to GitHub
issues below) which caused:

1. Values of enum fields were being removed from JsonAdapters
2. Breaking of JSON parsing where an interface is implemented by
multiple response types (See DataPullResponse and its class hierarchy)

Problem #1 is easy to fix by adding "@JsonClass(generateAdapter=false)"
to all enums, but #2 is an invasive change since that will require us
to change how our sync works.

In addition, the Sentry gradle plugin that we were using was
incompatible with AGP 3.4.0 (or rather, Gradle 5.+ which AGP 3.4.0
requires).

Changes:
- Disable R8 for now and raise an issue on the Moshi repository regd
how to make it work with R8.
- Update the Sentry plugin to the latest version

See square/retrofit#3005
autorebase bot pushed a commit to simpledotorg/simple-android that referenced this issue May 16, 2019
Reason:
When we moved to Android Gradle Plugin(AGP) 3.4.0, it switched from
using Proguard for minification to R8. There were a couple of
compatibility issues with Moshi, Retrofit, and R8 (links to GitHub
issues below) which caused:

1. Values of enum fields were being removed from JsonAdapters
2. Breaking of JSON parsing where an interface is implemented by
multiple response types (See DataPullResponse and its class hierarchy)

Problem #1 is easy to fix by adding "@JsonClass(generateAdapter=false)"
to all enums, but #2 is an invasive change since that will require us
to change how our sync works.

In addition, the Sentry gradle plugin that we were using was
incompatible with AGP 3.4.0 (or rather, Gradle 5.+ which AGP 3.4.0
requires).

Changes:
- Disable R8 for now and raise an issue on the Moshi repository regd
how to make it work with R8.
- Update the Sentry plugin to the latest version

See square/retrofit#3005
@buntupana
Copy link

buntupana commented Apr 20, 2023

Hi @JakeWharton , I've just updated to gradle 8 and I'm getting this error when using R8 in full mode

IllegalArgumentException: Response must include generic type (e.g., Response<String>)

That happen in every call the app trying to do, when R8 full mode is disabled it's working well.

@mirekkukla
Copy link

mirekkukla commented Apr 26, 2023

I'm likewise seeing this become a problem again after I've updated to gradle 8 (I'm on the latest retrofit version v2.9.0). In my case the exception reads:

java.lang.IllegalArgumentException: Unable to create call adapter for interface wc.g

If I disable full mode everything works as expected.

Revisiting the workaround suggested for the original manifestation of this bug works, ie if I add the following rule everything works as expected even in full model:

-if interface * { @retrofit2.http.* *** *(...); }
-keep,allowobfuscation interface <3>

UPDATE: adding the "Call" rule added in this comit likewise resolves the problem.

-keep,allowobfuscation,allowshrinking interface retrofit2.Call

It seems said commit has yet to be released.

@tprochazka
Copy link

Now, when R8 full mode is officially available for everyone, a new version of RetroFit that fix could be really released.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

9 participants
@JakeWharton @tprochazka @christofferqa @mirekkukla @buntupana and others