Android framework version
net10.0-android
Affected platform version
.NET 10.0.100
Description
We've been analyzing the proguard rules that are generated for our Android application during the build, and noticed that for types wrapped in Android Binding project there are much more "keep class" and "keepclassmembers" rules generated then expected.
Expected result:
If Main Application is using TrimMode=full and AndroidLinkTool=r8, then only actually used Java classes and members should be explicitly preserved as reachable from managed code.
Actual result:
For all reachable Java classes all methods are explicitly preserved via generated proguard files, which in turn may preserve classes that are not reachable from managed code.
Steps to Reproduce
Here's the simplified example, similar to to what we have in our application:
- Android Binding project is marked with
IsTrimmable=true and distributed as NuGet package. It wraps Java API similar to following:
package com.third.party;
class Sdk {
static Sdk getInstance();
void initialize(String key);
Service getService();
}
interface Service {
void load(Size size);
}
class Size {
int getHeight();
int getWidth();
}
- MainApplication is using
TrimMode=full and AndroidLinkTool=r8. It imports the NuGet package with bindings and uses it the following way (no other APIs are used):
Sdk.GetInstance().Initialize("sdk_key");
- After the build, we see the following generated rules
In proguard_project_references.cfg:
-keep class com.third.party.Sdk
-keepclassmembers class com.third.party.Sdk {
*** getService(...);
*** getInstance(...);
*** initialize(...);
}
-keep class com.third.party.Service
-keepclassmembers class com.third.party.Service {
}
-keep class com.third.party.Size
-keepclassmembers class com.third.party.Size {
*** getHeight(...);
*** getWidth(...);
}
In proguard_project_primary.cfg:
-keep class com.third.party.Service { *; }
-keep class com.third.party.Service { *; }
-keep class com.third.party.Service { *; }
So not only the unused Sdk.getService() method was preserved, it also started a chain that preserved other unused types. I would also assume that corresponding managed types were not trimmed.
The culprit here might be MarkJavaObjects task, specifically how methods are selected for preservation:
|
if (IsUserType (type) && type.HasMethods) { |
|
foreach (var method in type.Methods.Where (m => m.Overrides != null)) |
|
PreserveMethod (type, method); |
It uses condition type.Methods.Where(m => m.Overrides != null) to select methods, but maybe better way would be to check for type.Methods.Where(m => m.HasOverrides), as Overrides might return empty collection instead of null (https://github.com/jbevain/cecil/blob/882ca5eedda1e62eb41bd5869aeb15d8f1538e51/Mono.Cecil/MethodDefinition.cs#L222-L243)
Did you find any workaround?
No response
Relevant log output
Android framework version
net10.0-android
Affected platform version
.NET 10.0.100
Description
We've been analyzing the proguard rules that are generated for our Android application during the build, and noticed that for types wrapped in Android Binding project there are much more "keep class" and "keepclassmembers" rules generated then expected.
Expected result:
If Main Application is using TrimMode=full and AndroidLinkTool=r8, then only actually used Java classes and members should be explicitly preserved as reachable from managed code.
Actual result:
For all reachable Java classes all methods are explicitly preserved via generated proguard files, which in turn may preserve classes that are not reachable from managed code.
Steps to Reproduce
Here's the simplified example, similar to to what we have in our application:
IsTrimmable=trueand distributed as NuGet package. It wraps Java API similar to following:TrimMode=fullandAndroidLinkTool=r8. It imports the NuGet package with bindings and uses it the following way (no other APIs are used):In
proguard_project_references.cfg:In
proguard_project_primary.cfg:So not only the unused
Sdk.getService()method was preserved, it also started a chain that preserved other unused types. I would also assume that corresponding managed types were not trimmed.The culprit here might be
MarkJavaObjectstask, specifically how methods are selected for preservation:android/src/Microsoft.Android.Sdk.ILLink/MarkJavaObjects.cs
Lines 94 to 96 in 2f6fe49
It uses condition
type.Methods.Where(m => m.Overrides != null)to select methods, but maybe better way would be to check fortype.Methods.Where(m => m.HasOverrides), asOverridesmight return empty collection instead ofnull(https://github.com/jbevain/cecil/blob/882ca5eedda1e62eb41bd5869aeb15d8f1538e51/Mono.Cecil/MethodDefinition.cs#L222-L243)Did you find any workaround?
No response
Relevant log output