Skip to content

Commit 14ab168

Browse files
jonathanpeppersjonpryor
authored andcommitted
[Xamarin.Android.Build.Tasks] fix proguard rules for R8 compat (dotnet#2735)
Fixes: dotnet#2684 Context: https://www.guardsquare.com/en/products/proguard/manual/usage#keepoptionmodifiers Context: https://www.guardsquare.com/en/products/proguard/manual/examples Using R8 on a pre-Android 8.0 device was crashing with: Unhandled Exception: Java.Lang.LinkageError: no non-static method "Landroid/runtime/UncaughtExceptionHandler;.<init>()V" at Java.Interop.JniEnvironment+InstanceMethods.GetMethodID (Java.Interop.JniObjectReference type, System.String name, System.String signature) [0x0005b] in <7d7bcc9ee9cc460db8abcdb9a9622733>:0 at Java.Interop.JniType.GetConstructor (System.String signature) [0x0000c] in <7d7bcc9ee9cc460db8abcdb9a9622733>:0 at Java.Interop.JniPeerMembers+JniInstanceMethods.GetConstructor (System.String signature) [0x00035] in <7d7bcc9ee9cc460db8abcdb9a9622733>:0 at Java.Interop.JniPeerMembers+JniInstanceMethods.FinishCreateInstance (System.String constructorSignature, Java.Interop.IJavaPeerable self, Java.Interop.JniArgumentValue* parameters) [0x00036] in <7d7bcc9ee9cc460db8abcdb9a9622733>:0 at Java.Lang.Object..ctor () [0x00054] in <d77389624c8c4948a12589c4bd4500eb>:0 at Android.Runtime.UncaughtExceptionHandler..ctor (Java.Lang.Thread+IUncaughtExceptionHandler defaultHandler) [0x00000] in <d77389624c8c4948a12589c4bd4500eb>:0 at Android.Runtime.JNIEnv.Initialize (Android.Runtime.JnienvInitializeArgs* args) [0x00202] in <d77389624c8c4948a12589c4bd4500eb>:0 --- End of managed Java.Lang.LinkageError stack trace --- java.lang.NoSuchMethodError: no non-static method "Landroid/runtime/UncaughtExceptionHandler;.<init>()V" at mono.android.Runtime.init(Native Method) at mono.MonoPackageManager.LoadApplication(:21) at mono.MonoRuntimeProvider.attachInfo(:1) at android.app.ActivityThread.installProvider(ActivityThread.java:5853) at android.app.ActivityThread.installContentProviders(ActivityThread.java:5445) at android.app.ActivityThread.handleBindApplication(ActivityThread.java:5384) at android.app.ActivityThread.-wrap2(ActivityThread.java) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1545) at android.os.Handler.dispatchMessage(Handler.java:102) at android.os.Looper.loop(Looper.java:154) at android.app.ActivityThread.main(ActivityThread.java:6119) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886) 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; 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. I reviewed [ProGuard's documented syntax/grammar][0]: [@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 *happened* to work, but is undocumented. R8 does not appear to accept the `(***)` syntax at all. Update `proguard_xamarin.cfg` so that it uses `(...)`, not `(***)`, allowing R8 to properly process `proguard_xamarin.cfg`. ~~ 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. [0]: https://www.guardsquare.com/en/products/proguard/manual/usage#classspecification
1 parent 23c3005 commit 14ab168

File tree

3 files changed

+99
-14
lines changed

3 files changed

+99
-14
lines changed

src/Xamarin.Android.Build.Tasks/Resources/proguard_xamarin.cfg

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,15 @@
1414
-keep class opentk_1_0.GameViewBase { *; <init>(...); }
1515
-keep class com.xamarin.java_interop.ManagedPeer { *; <init>(...); }
1616

17-
-keep class android.runtime.** { <init>(***); }
18-
-keep class assembly_mono_android.android.runtime.** { <init>(***); }
17+
-keep class android.runtime.** { <init>(...); }
18+
-keep class assembly_mono_android.android.runtime.** { <init>(...); }
1919
# hash for android.runtime and assembly_mono_android.android.runtime.
2020
-keep class md52ce486a14f4bcd95899665e9d932190b.** { *; <init>(...); }
2121
-keepclassmembers class md52ce486a14f4bcd95899665e9d932190b.** { *; <init>(...); }
2222

2323
# Android's template misses fluent setters...
2424
-keepclassmembers class * extends android.view.View {
25-
*** set*(***);
25+
*** set*(...);
2626
}
2727

2828
# also misses those inflated custom layout stuff from xml...

src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -774,10 +774,15 @@ public void BuildProguardEnabledProject ([Values (true, false)] bool isRelease,
774774
Assert.IsTrue (StringAssertEx.ContainsText (File.ReadAllLines (proguardProjectPrimary), "-keep class md52d9cf6333b8e95e8683a477bc589eda5.MainActivity"), "`md52d9cf6333b8e95e8683a477bc589eda5.MainActivity` should exist in `proguard_project_primary.cfg`!");
775775
}
776776

777-
var className = "Lmono/MonoRuntimeProvider;";
778777
var dexFile = b.Output.GetIntermediaryPath (Path.Combine ("android", "bin", "classes.dex"));
779778
FileAssert.Exists (dexFile);
780-
Assert.IsTrue (DexUtils.ContainsClass (className, dexFile, b.AndroidSdkDirectory), $"`{dexFile}` should include `{className}`!");
779+
var classes = new [] {
780+
"Lmono/MonoRuntimeProvider;",
781+
"Landroid/runtime/UncaughtExceptionHandler;",
782+
};
783+
foreach (var className in classes) {
784+
Assert.IsTrue (DexUtils.ContainsClassWithMethod (className, "<init>", "()V", dexFile, b.AndroidSdkDirectory), $"`{dexFile}` should include `{className}`!");
785+
}
781786
}
782787
}
783788

src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Utilities/DexUtils.cs

Lines changed: 89 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,60 @@ namespace Xamarin.ProjectTools
99
{
1010
public static class DexUtils
1111
{
12+
/*
13+
Example dexdump output:
14+
15+
Class #12 -
16+
Class descriptor : 'Landroid/runtime/UncaughtExceptionHandler;'
17+
Access flags : 0x0001 (PUBLIC)
18+
Superclass : 'Ljava/lang/Object;'
19+
Interfaces -
20+
#0 : 'Ljava/lang/Thread$UncaughtExceptionHandler;'
21+
#1 : 'Lmono/android/IGCUserPeer;'
22+
Static fields -
23+
#0 : (in Landroid/runtime/UncaughtExceptionHandler;)
24+
name : '__md_methods'
25+
type : 'Ljava/lang/String;'
26+
access : 0x0019 (PUBLIC STATIC FINAL)
27+
Instance fields -
28+
#0 : (in Landroid/runtime/UncaughtExceptionHandler;)
29+
name : 'refList'
30+
type : 'Ljava/util/ArrayList;'
31+
access : 0x0002 (PRIVATE)
32+
Direct methods -
33+
#0 : (in Landroid/runtime/UncaughtExceptionHandler;)
34+
name : '<clinit>'
35+
type : '()V'
36+
access : 0x10008 (STATIC CONSTRUCTOR)
37+
code -
38+
registers : 3
39+
ins : 0
40+
outs : 3
41+
insns size : 10 16-bit code units
42+
catches : (none)
43+
positions :
44+
0x0002 line=16
45+
locals :
46+
#1 : (in Landroid/runtime/UncaughtExceptionHandler;)
47+
name : '<init>'
48+
type : '()V'
49+
access : 0x10001 (PUBLIC CONSTRUCTOR)
50+
code -
51+
registers : 4
52+
ins : 1
53+
outs : 4
54+
insns size : 22 16-bit code units
55+
catches : (none)
56+
positions :
57+
0x0000 line=22
58+
0x0003 line=23
59+
0x0010 line=24
60+
locals :
61+
0x0000 - 0x0016 reg=3 this Landroid/runtime/UncaughtExceptionHandler;
62+
*/
63+
1264
/// <summary>
1365
/// Runs the dexdump command to see if a class exists in a dex file
14-
/// dexdump returns data of the form:
15-
/// Class descriptor : 'Landroid/app/ActivityTracker;'
1666
/// </summary>
1767
/// <param name="className">A Java class name of the form 'Landroid/app/ActivityTracker;'</param>
1868
public static bool ContainsClass (string className, string dexFile, string androidSdkDirectory)
@@ -22,13 +72,45 @@ public static bool ContainsClass (string className, string dexFile, string andro
2272
if (e.Data != null && e.Data.Contains ("Class descriptor") && e.Data.Contains (className))
2373
containsClass = true;
2474
};
75+
DexDump (handler, dexFile, androidSdkDirectory);
76+
return containsClass;
77+
}
2578

26-
var androidSdk = new AndroidSdkInfo ((l, m) => {
27-
Console.WriteLine ($"{l}: {m}");
28-
if (l == TraceLevel.Error) {
29-
throw new Exception (m);
79+
/// <summary>
80+
/// Runs the dexdump command to see if a class exists in a dex file *and* has a public constructor
81+
/// </summary>
82+
/// <param name="className">A Java class name of the form 'Landroid/app/ActivityTracker;'</param>
83+
/// <param name="method">A Java method name of the form 'foo'</param>
84+
/// <param name="type">A Java method signature of the form '()V'</param>
85+
public static bool ContainsClassWithMethod (string className, string method, string type, string dexFile, string androidSdkDirectory)
86+
{
87+
bool inClass = false;
88+
bool hasName = false;
89+
bool hasType = false;
90+
DataReceivedEventHandler handler = (s, e) => {
91+
if (e.Data != null) {
92+
if (e.Data.Contains ("Class descriptor")) {
93+
inClass = e.Data.Contains (className);
94+
hasName = false;
95+
} else if (inClass && e.Data.Contains ("name") && e.Data.Contains (method)) {
96+
hasName = true;
97+
} else if (hasName && e.Data.Contains ("type") && e.Data.Contains (type)) {
98+
hasType = true;
3099
}
31-
}, androidSdkDirectory);
100+
}
101+
};
102+
DexDump (handler, dexFile, androidSdkDirectory);
103+
return hasType;
104+
}
105+
106+
static void DexDump (DataReceivedEventHandler handler, string dexFile, string androidSdkDirectory)
107+
{
108+
var androidSdk = new AndroidSdkInfo ((l, m) => {
109+
Console.WriteLine ($"{l}: {m}");
110+
if (l == TraceLevel.Error) {
111+
throw new Exception (m);
112+
}
113+
}, androidSdkDirectory);
32114
var buildToolsPath = androidSdk.GetBuildToolsPaths ().FirstOrDefault ();
33115
if (string.IsNullOrEmpty (buildToolsPath)) {
34116
throw new Exception ($"Unable to find build-tools in `{androidSdkDirectory}`!");
@@ -53,8 +135,6 @@ public static bool ContainsClass (string className, string dexFile, string andro
53135
p.BeginOutputReadLine ();
54136
p.WaitForExit ();
55137
}
56-
57-
return containsClass;
58138
}
59139
}
60140
}

0 commit comments

Comments
 (0)