Skip to content
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

[Xamarin.Android.Build.Tasks] fix proguard rules for R8 compatibility #2735

Merged
merged 1 commit into from
Feb 14, 2019

Conversation

jonathanpeppers
Copy link
Member

Context: https://www.guardsquare.com/en/products/proguard/manual/usage#keepoptionmodifiers
Context: https://www.guardsquare.com/en/products/proguard/manual/examples
Fixes: #2684

Using R8 on a pre-Android 8.0 device was crashing with:

E/mono    (16220): Unhandled Exception:
E/mono    (16220): Java.Lang.LinkageError: no non-static method "Landroid/runtime/UncaughtExceptionHandler;.<init>()V"
E/mono    (16220):   at Java.Interop.JniEnvironment+InstanceMethods.GetMethodID (Java.Interop.JniObjectReference type, System.String name, System.String signature) [0x0005b] in <7d7bcc9ee9cc460db8abcdb9a9622733>:0
E/mono    (16220):   at Java.Interop.JniType.GetConstructor (System.String signature) [0x0000c] in <7d7bcc9ee9cc460db8abcdb9a9622733>:0
E/mono    (16220):   at Java.Interop.JniPeerMembers+JniInstanceMethods.GetConstructor (System.String signature) [0x00035] in <7d7bcc9ee9cc460db8abcdb9a9622733>:0
E/mono    (16220):   at Java.Interop.JniPeerMembers+JniInstanceMethods.FinishCreateInstance (System.String constructorSignature, Java.Interop.IJavaPeerable self, Java.Interop.JniArgumentValue* parameters) [0x00036] in <7d7bcc9ee9cc460db8abcdb9a9622733>:0
E/mono    (16220):   at Java.Lang.Object..ctor () [0x00054] in <d77389624c8c4948a12589c4bd4500eb>:0
E/mono    (16220):   at Android.Runtime.UncaughtExceptionHandler..ctor (Java.Lang.Thread+IUncaughtExceptionHandler defaultHandler) [0x00000] in <d77389624c8c4948a12589c4bd4500eb>:0
E/mono    (16220):   at Android.Runtime.JNIEnv.Initialize (Android.Runtime.JnienvInitializeArgs* args) [0x00202] in <d77389624c8c4948a12589c4bd4500eb>:0
E/mono    (16220):   --- End of managed Java.Lang.LinkageError stack trace ---
E/mono    (16220): java.lang.NoSuchMethodError: no non-static method "Landroid/runtime/UncaughtExceptionHandler;.<init>()V"
E/mono    (16220): 	at mono.android.Runtime.init(Native Method)
E/mono    (16220): 	at mono.MonoPackageManager.LoadApplication(:21)
E/mono    (16220): 	at mono.MonoRuntimeProvider.attachInfo(:1)
E/mono    (16220): 	at android.app.ActivityThread.installProvider(ActivityThread.java:5853)
E/mono    (16220): 	at android.app.ActivityThread.installContentProviders(ActivityThread.java:5445)
E/mono    (16220): 	at android.app.ActivityThread.handleBindApplication(ActivityThread.java:5384)
E/mono    (16220): 	at android.app.ActivityThread.-wrap2(ActivityThread.java)
E/mono    (16220): 	at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1545)
E/mono    (16220): 	at android.os.Handler.dispatchMessage(Handler.java:102)
E/mono    (16220): 	at android.os.Looper.loop(Looper.java:154)
E/mono    (16220): 	at android.app.ActivityThread.main(ActivityThread.java:6119)
E/mono    (16220): 	at java.lang.reflect.Method.invoke(Native Method)
E/mono    (16220): 	at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
E/mono    (16220): 	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)

Looking into it, the following method was present when using ProGuard,
but not R8:

#1              : (in Landroid/runtime/UncaughtExceptionHandler;)
  name          : '<init>'
  type          : '()V'
  access        : 0x10001 (PUBLIC CONSTRUCTOR)
  code          -
  registers     : 4
  ins           : 1
  outs          : 4
  insns size    : 22 16-bit code units
  catches       : (none)
  positions     :
    0x0000 line=22
    0x0003 line=23
    0x0010 line=24
  locals        :
    0x0000 - 0x0016 reg=3 this Landroid/runtime/UncaughtExceptionHandler;

Then I looked at proguard_xamarin.cfg, and noticed something strange:

-keep class android.runtime.** { <init>(***); }

It seemed this rule was being ignored? All the other working rules
were using <init>(...); to refer to the instance constructor.

When I reviewed ProGuard's documented syntax/grammar:

[@annotationtype] [[!]public|final|abstract|@ ...] [!]interface|class|enum classname
    [extends|implements [@annotationtype] classname]
[{
    [@annotationtype] [[!]public|private|protected|static|volatile|transient ...] <fields> |
                                                                    (fieldtype fieldname);
    [@annotationtype] [[!]public|private|protected|static|synchronized|native|abstract|strictfp ...] <methods> |
                                                                                        <init>(argumenttype,...) |
                                                                                        classname(argumenttype,...) |
                                                                                        (returntype methodname(argumenttype,...));
    [@annotationtype] [[!]public|private|protected|static ... ] *;
    ...
}]

It seems (...) is the proper way to specify a match against any set
of arguments. Looking through ProGuard's examples, I can't find any
usage of (***). It must happen to work, but is undocumented.

R8 does not appear to accept the (***) syntax at all.

Other changes

I expanded upon the DexUtils class so it can look for methods within
classes. I also adjusted one test to verify this problem is solved.

For comparison, attached dexdump output

Context: https://www.guardsquare.com/en/products/proguard/manual/usage#keepoptionmodifiers
Context: https://www.guardsquare.com/en/products/proguard/manual/examples
Fixes: dotnet#2684

Using R8 on a pre-Android 8.0 device was crashing with:

    E/mono    (16220): Unhandled Exception:
    E/mono    (16220): Java.Lang.LinkageError: no non-static method "Landroid/runtime/UncaughtExceptionHandler;.<init>()V"
    E/mono    (16220):   at Java.Interop.JniEnvironment+InstanceMethods.GetMethodID (Java.Interop.JniObjectReference type, System.String name, System.String signature) [0x0005b] in <7d7bcc9ee9cc460db8abcdb9a9622733>:0
    E/mono    (16220):   at Java.Interop.JniType.GetConstructor (System.String signature) [0x0000c] in <7d7bcc9ee9cc460db8abcdb9a9622733>:0
    E/mono    (16220):   at Java.Interop.JniPeerMembers+JniInstanceMethods.GetConstructor (System.String signature) [0x00035] in <7d7bcc9ee9cc460db8abcdb9a9622733>:0
    E/mono    (16220):   at Java.Interop.JniPeerMembers+JniInstanceMethods.FinishCreateInstance (System.String constructorSignature, Java.Interop.IJavaPeerable self, Java.Interop.JniArgumentValue* parameters) [0x00036] in <7d7bcc9ee9cc460db8abcdb9a9622733>:0
    E/mono    (16220):   at Java.Lang.Object..ctor () [0x00054] in <d77389624c8c4948a12589c4bd4500eb>:0
    E/mono    (16220):   at Android.Runtime.UncaughtExceptionHandler..ctor (Java.Lang.Thread+IUncaughtExceptionHandler defaultHandler) [0x00000] in <d77389624c8c4948a12589c4bd4500eb>:0
    E/mono    (16220):   at Android.Runtime.JNIEnv.Initialize (Android.Runtime.JnienvInitializeArgs* args) [0x00202] in <d77389624c8c4948a12589c4bd4500eb>:0
    E/mono    (16220):   --- End of managed Java.Lang.LinkageError stack trace ---
    E/mono    (16220): java.lang.NoSuchMethodError: no non-static method "Landroid/runtime/UncaughtExceptionHandler;.<init>()V"
    E/mono    (16220): 	at mono.android.Runtime.init(Native Method)
    E/mono    (16220): 	at mono.MonoPackageManager.LoadApplication(:21)
    E/mono    (16220): 	at mono.MonoRuntimeProvider.attachInfo(:1)
    E/mono    (16220): 	at android.app.ActivityThread.installProvider(ActivityThread.java:5853)
    E/mono    (16220): 	at android.app.ActivityThread.installContentProviders(ActivityThread.java:5445)
    E/mono    (16220): 	at android.app.ActivityThread.handleBindApplication(ActivityThread.java:5384)
    E/mono    (16220): 	at android.app.ActivityThread.-wrap2(ActivityThread.java)
    E/mono    (16220): 	at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1545)
    E/mono    (16220): 	at android.os.Handler.dispatchMessage(Handler.java:102)
    E/mono    (16220): 	at android.os.Looper.loop(Looper.java:154)
    E/mono    (16220): 	at android.app.ActivityThread.main(ActivityThread.java:6119)
    E/mono    (16220): 	at java.lang.reflect.Method.invoke(Native Method)
    E/mono    (16220): 	at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
    E/mono    (16220): 	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)

Looking into it, the following method was present when using ProGuard,
but not R8:

    #1              : (in Landroid/runtime/UncaughtExceptionHandler;)
      name          : '<init>'
      type          : '()V'
      access        : 0x10001 (PUBLIC CONSTRUCTOR)
      code          -
      registers     : 4
      ins           : 1
      outs          : 4
      insns size    : 22 16-bit code units
      catches       : (none)
      positions     :
        0x0000 line=22
        0x0003 line=23
        0x0010 line=24
      locals        :
        0x0000 - 0x0016 reg=3 this Landroid/runtime/UncaughtExceptionHandler;

Then I looked at `proguard_xamarin.cfg`, and noticed something strange:

    -keep class android.runtime.** { <init>(***); }

It seemed this rule was being ignored? All the other *working* rules
were using `<init>(...);` to refer to the instance constructor.

When I reviewed ProGuard's documented syntax/grammar:

    [@annotationtype] [[!]public|final|abstract|@ ...] [!]interface|class|enum classname
        [extends|implements [@annotationtype] classname]
    [{
        [@annotationtype] [[!]public|private|protected|static|volatile|transient ...] <fields> |
                                                                        (fieldtype fieldname);
        [@annotationtype] [[!]public|private|protected|static|synchronized|native|abstract|strictfp ...] <methods> |
                                                                                            <init>(argumenttype,...) |
                                                                                            classname(argumenttype,...) |
                                                                                            (returntype methodname(argumenttype,...));
        [@annotationtype] [[!]public|private|protected|static ... ] *;
        ...
    }]

It seems `(...)` is the proper way to specify a match against any set
of arguments. Looking through ProGuard's examples, I can't find any
usage of `(***)`. It must *happen* to work, but is undocumented.

R8 does not appear to accept the `(***)` syntax at all.

~~ Other changes ~~

I expanded upon the `DexUtils` class so it can look for methods within
classes. I also adjusted one test to verify this problem is solved.
@jonpryor jonpryor merged commit 14ab168 into dotnet:master Feb 14, 2019
@jonathanpeppers jonathanpeppers deleted the r8-proguard-rules branch April 1, 2019 13:31
@github-actions github-actions bot locked and limited conversation to collaborators Feb 1, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants